import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { createRequire } from 'node:module'; import { test } from 'node:test'; import React from 'react'; import { act, create } from 'react-test-renderer'; import ts from 'typescript'; import * as chatModel from '../src/lib/chat.ts'; globalThis.IS_REACT_ACT_ENVIRONMENT = true; const require = createRequire(import.meta.url); function harness() { let resolvePicker; const picker = new Promise(resolve => { resolvePicker = resolve; }); const drafts = []; const chat = { detail: { name: 'Test', canSend: true }, messages: [], members: [], reactions: {}, loading: false }; const output = ts.transpileModule(readFileSync(new URL('../components/chat-conversation.tsx', import.meta.url), 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX, target: ts.ScriptTarget.ES2022 }, }).outputText; const exports = {}; new Function('require', 'exports', output)(name => { if (name === 'react-native') return { ...Object.fromEntries(['ActivityIndicator', 'FlatList', 'Modal', 'Pressable', 'ScrollView', 'Text', 'TextInput', 'View'].map(key => [key, key])), StyleSheet: { create: value => value } }; if (name === '@expo/vector-icons') return { Ionicons: 'Icon' }; if (name === 'expo-image') return { Image: 'Image' }; if (name === 'expo-image-picker') return { launchImageLibraryAsync: () => picker }; if (name === '@/hooks/use-chat-conversation') return { useChatConversation: () => chat }; if (name === '@/src/lib/chat-api') return { chatApi: {} }; if (name === '@/src/lib/chat-image') return { prepareMobileChatImage: async () => ({ name: 'Bild.jpg', dataUrl: 'data:image/jpeg;base64,YQ==' }) }; if (name === '@/src/lib/chat') return chatModel; return require(name); }, exports); const props = { token: 't', room: { key: 'test', name: 'Test' }, enabled: true, draft: { text: 'Bild dazu', image: null, clientId: 'draft' }, onDraft: draft => drafts.push(draft) }; let root; return { drafts, async render(enabled) { await act(async () => { const element = React.createElement(exports.ChatConversation, { ...props, enabled }); if (root) root.update(element); else root = create(element); }); }, async pick() { await act(async () => root.root.findByProps({ accessibilityLabel: 'Bild anhängen' }).props.onPress()); }, async finish() { await act(async () => resolvePicker({ canceled: false, assets: [{ uri: 'file:///test.jpg' }] })); }, async dispose() { await act(async () => root.unmount()); }, }; } test('System-Fotoauswahl darf die App vorübergehend in den Hintergrund versetzen', async () => { const h = harness(); try { await h.render(true); await h.pick(); await h.render(false); await h.finish(); assert.equal(h.drafts.length, 1); assert.equal(h.drafts[0].text, 'Bild dazu'); assert.equal(h.drafts[0].image.name, 'Bild.jpg'); } finally { await h.dispose(); } }); test('Eine verspätete Fotoauswahl verändert nach Raumwechsel keinen anderen Entwurf', async () => { const h = harness(); await h.render(true); await h.pick(); await h.dispose(); await h.finish(); assert.equal(h.drafts.length, 0); });