|
1
|
// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved. |
|
2
|
// Use of this source code is governed by the MIT license. |
|
3
|
// |
|
4
|
import 'package:flutter_test/flutter_test.dart'; |
|
5
|
import 'package:onde_inference/onde_inference.dart'; |
|
6
|
|
|
7
|
void main() { |
|
8
|
group('OndeException', () { |
|
9
|
test('toString includes error info', () { |
|
10
|
const e = OndeException(OndeError_NoModelLoaded()); |
|
11
|
expect(e.toString(), contains('OndeException')); |
|
12
|
}); |
|
13
|
|
|
14
|
test('equality', () { |
|
15
|
const a = OndeException(OndeError_NoModelLoaded()); |
|
16
|
const b = OndeException(OndeError_NoModelLoaded()); |
|
17
|
const c = OndeException(OndeError_Cancelled()); |
|
18
|
expect(a, equals(b)); |
|
19
|
expect(a, isNot(equals(c))); |
|
20
|
}); |
|
21
|
}); |
|
22
|
|
|
23
|
group('ChatRole', () { |
|
24
|
test('name values', () { |
|
25
|
expect(ChatRole.system.name, 'system'); |
|
26
|
expect(ChatRole.user.name, 'user'); |
|
27
|
expect(ChatRole.assistant.name, 'assistant'); |
|
28
|
}); |
|
29
|
}); |
|
30
|
|
|
31
|
group('ChatMessage', () { |
|
32
|
test('factory constructors set correct roles', () { |
|
33
|
expect(ChatMessageX.system('Hi').role, ChatRole.system); |
|
34
|
expect(ChatMessageX.user('Hi').role, ChatRole.user); |
|
35
|
expect(ChatMessageX.assistant('Hi').role, ChatRole.assistant); |
|
36
|
}); |
|
37
|
|
|
38
|
test('equality', () { |
|
39
|
final a = ChatMessageX.user('Hello'); |
|
40
|
final b = ChatMessageX.user('Hello'); |
|
41
|
final c = ChatMessageX.user('World'); |
|
42
|
expect(a, equals(b)); |
|
43
|
expect(a, isNot(equals(c))); |
|
44
|
}); |
|
45
|
}); |
|
46
|
|
|
47
|
group('SamplingConfig', () { |
|
48
|
test('defaultConfig preset', () { |
|
49
|
final s = SamplingConfigX.defaultConfig(); |
|
50
|
expect(s.temperature, 0.7); |
|
51
|
expect(s.topP, 0.95); |
|
52
|
expect(s.maxTokens, BigInt.from(512)); |
|
53
|
}); |
|
54
|
|
|
55
|
test('deterministic preset', () { |
|
56
|
final s = SamplingConfigX.deterministic(); |
|
57
|
expect(s.temperature, 0.0); |
|
58
|
expect(s.maxTokens, BigInt.from(512)); |
|
59
|
expect(s.topP, isNull); |
|
60
|
}); |
|
61
|
|
|
62
|
test('mobile preset', () { |
|
63
|
final s = SamplingConfigX.mobile(); |
|
64
|
expect(s.temperature, 0.7); |
|
65
|
expect(s.topP, 0.95); |
|
66
|
expect(s.maxTokens, BigInt.from(128)); |
|
67
|
}); |
|
68
|
|
|
69
|
test('copyWith overrides single field', () { |
|
70
|
final base = SamplingConfigX.defaultConfig(); |
|
71
|
final copy = base.copyWith(temperature: 0.3); |
|
72
|
expect(copy.temperature, 0.3); |
|
73
|
expect(copy.topP, base.topP); |
|
74
|
expect(copy.maxTokens, base.maxTokens); |
|
75
|
}); |
|
76
|
|
|
77
|
test('equality', () { |
|
78
|
expect( |
|
79
|
SamplingConfigX.deterministic(), |
|
80
|
equals(SamplingConfigX.deterministic()), |
|
81
|
); |
|
82
|
expect( |
|
83
|
SamplingConfigX.deterministic(), |
|
84
|
isNot(equals(SamplingConfigX.mobile())), |
|
85
|
); |
|
86
|
}); |
|
87
|
}); |
|
88
|
|
|
89
|
group('GgufModelConfig', () { |
|
90
|
test('factory functions return non-empty configs', () { |
|
91
|
final config1_5b = OndeInference.qwen2515bConfig(); |
|
92
|
expect(config1_5b.modelId, contains('1.5B')); |
|
93
|
expect(config1_5b.files, isNotEmpty); |
|
94
|
expect(config1_5b.displayName, isNotEmpty); |
|
95
|
expect(config1_5b.approxMemory, isNotEmpty); |
|
96
|
|
|
97
|
final config3b = OndeInference.qwen253bConfig(); |
|
98
|
expect(config3b.modelId, contains('3B')); |
|
99
|
expect(config3b.files, isNotEmpty); |
|
100
|
|
|
101
|
final configCoder1_5b = OndeInference.qwen25Coder15bConfig(); |
|
102
|
expect(configCoder1_5b.modelId, contains('Coder')); |
|
103
|
|
|
104
|
final configCoder3b = OndeInference.qwen25Coder3bConfig(); |
|
105
|
expect(configCoder3b.modelId, contains('Coder')); |
|
106
|
|
|
107
|
final defaultConfig = OndeInference.defaultModelConfig(); |
|
108
|
expect(defaultConfig.modelId, isNotEmpty); |
|
109
|
}); |
|
110
|
|
|
111
|
test('equality', () { |
|
112
|
expect( |
|
113
|
OndeInference.qwen2515bConfig(), |
|
114
|
equals(OndeInference.qwen2515bConfig()), |
|
115
|
); |
|
116
|
expect( |
|
117
|
OndeInference.qwen2515bConfig(), |
|
118
|
isNot(equals(OndeInference.qwen253bConfig())), |
|
119
|
); |
|
120
|
}); |
|
121
|
}); |
|
122
|
|
|
123
|
group('EngineStatus', () { |
|
124
|
test('isReady only true for ready', () { |
|
125
|
expect(EngineStatus.ready.isReady, isTrue); |
|
126
|
expect(EngineStatus.unloaded.isReady, isFalse); |
|
127
|
expect(EngineStatus.loading.isReady, isFalse); |
|
128
|
expect(EngineStatus.generating.isReady, isFalse); |
|
129
|
expect(EngineStatus.error.isReady, isFalse); |
|
130
|
}); |
|
131
|
|
|
132
|
test('name values', () { |
|
133
|
expect(EngineStatus.unloaded.name, 'unloaded'); |
|
134
|
expect(EngineStatus.loading.name, 'loading'); |
|
135
|
expect(EngineStatus.ready.name, 'ready'); |
|
136
|
expect(EngineStatus.generating.name, 'generating'); |
|
137
|
expect(EngineStatus.error.name, 'error'); |
|
138
|
}); |
|
139
|
}); |
|
140
|
|
|
141
|
group('EngineInfo', () { |
|
142
|
test('equality', () { |
|
143
|
final a = EngineInfo( |
|
144
|
status: EngineStatus.unloaded, |
|
145
|
historyLength: BigInt.zero, |
|
146
|
); |
|
147
|
final b = EngineInfo( |
|
148
|
status: EngineStatus.unloaded, |
|
149
|
historyLength: BigInt.zero, |
|
150
|
); |
|
151
|
final c = EngineInfo( |
|
152
|
status: EngineStatus.ready, |
|
153
|
modelName: 'Qwen 2.5 3B', |
|
154
|
approxMemory: '~1.93 GB', |
|
155
|
historyLength: BigInt.from(3), |
|
156
|
); |
|
157
|
expect(a, equals(b)); |
|
158
|
expect(a, isNot(equals(c))); |
|
159
|
}); |
|
160
|
}); |
|
161
|
|
|
162
|
group('StreamChunk', () { |
|
163
|
test('equality', () { |
|
164
|
const a = StreamChunk(delta: 'hello', done: false); |
|
165
|
const b = StreamChunk(delta: 'hello', done: false); |
|
166
|
const c = StreamChunk(delta: 'bye', done: true, finishReason: 'stop'); |
|
167
|
expect(a, equals(b)); |
|
168
|
expect(a, isNot(equals(c))); |
|
169
|
}); |
|
170
|
}); |
|
171
|
|
|
172
|
group('InferenceResult', () { |
|
173
|
test('equality', () { |
|
174
|
const a = InferenceResult( |
|
175
|
text: 'Hi there!', |
|
176
|
durationSecs: 1.5, |
|
177
|
durationDisplay: '1.5s', |
|
178
|
finishReason: 'stop', |
|
179
|
toolCalls: [], |
|
180
|
); |
|
181
|
const b = InferenceResult( |
|
182
|
text: 'Hi there!', |
|
183
|
durationSecs: 1.5, |
|
184
|
durationDisplay: '1.5s', |
|
185
|
finishReason: 'stop', |
|
186
|
toolCalls: [], |
|
187
|
); |
|
188
|
expect(a, equals(b)); |
|
189
|
}); |
|
190
|
}); |
|
191
|
|
|
192
|
group('RustLib', () { |
|
193
|
test('instance is accessible before init', () { |
|
194
|
// instance is always non-null; initialization requires the native |
|
195
|
// library to be compiled first. |
|
196
|
expect(RustLib.instance, isNotNull); |
|
197
|
}); |
|
198
|
}); |
|
199
|
|
|
200
|
group('OndeInference helpers', () { |
|
201
|
test('init() delegates to RustLib.init()', () async { |
|
202
|
await expectLater(OndeInference.init(), completes); |
|
203
|
}); |
|
204
|
|
|
205
|
test('config factories return valid configs', () { |
|
206
|
expect(OndeInference.defaultModelConfig().modelId, isNotEmpty); |
|
207
|
expect(OndeInference.qwen2515bConfig().modelId, contains('1.5B')); |
|
208
|
expect(OndeInference.qwen253bConfig().modelId, contains('3B')); |
|
209
|
expect(OndeInference.qwen25Coder15bConfig().modelId, contains('Coder')); |
|
210
|
expect(OndeInference.qwen25Coder3bConfig().modelId, contains('Coder')); |
|
211
|
}); |
|
212
|
|
|
213
|
test('sampling factories return correct presets', () { |
|
214
|
expect(OndeInference.defaultSamplingConfig().temperature, 0.7); |
|
215
|
expect(OndeInference.deterministicSamplingConfig().temperature, 0.0); |
|
216
|
expect(OndeInference.mobileSamplingConfig().maxTokens, BigInt.from(128)); |
|
217
|
}); |
|
218
|
}); |
|
219
|
|
|
220
|
group('OndeChatEngine', () { |
|
221
|
test('constructor throws when library not initialized', () { |
|
222
|
expect(() => OndeChatEngine(), throwsA(anything)); |
|
223
|
}); |
|
224
|
}); |
|
225
|
} |