fluffychat/test/pangea/igc_response_model_test.dart
wcjord dec473d579
Writing assistance (#5598)
* feat: wa working full stack

* feat: writing assistance made anew

* docs: migrate copilot docs to .github/instructions/ format

- Create choreographer.instructions.md (applyTo: lib/pangea/choreographer/**)
- Create events-and-tokens.instructions.md (applyTo: lib/pangea/events/**,lib/pangea/extensions/**)
- Create modules.instructions.md (applyTo: lib/pangea/**) — full module map
- Track copilot-instructions.md (remove .gitignore rule)
- Add documentation reference table to copilot-instructions.md

Content sourced from docs/copilot/ on writing-assistance branch.

* docs: remove old docs/copilot/ (migrated to .github/instructions/)

* docs: update choreographer + modules docs for writing-assistance audit

- Mark IT (Interactive Translation) as deprecated throughout
- Document new ReplacementTypeEnum taxonomy (grammar, surface, word-choice categories)
- Add AssistanceStateEnum, AutocorrectPopup, feedback rerun flow
- Mark SpanDataRepo/span_details as dead code
- Mark SpanChoiceTypeEnum.bestCorrection/bestAnswer as deprecated
- Add new files to modules listing (autocorrect_popup, start_igc_button, etc.)
- Update API endpoints table with active/deprecated/dead status

* formatting, replace deprecated withOpacity calls

* fix linter issues from deprecated types

* use better error color

* move cloing of overlays into choreographer

* reduce duplicate code on igc_controller, update UI on feedback

* couple of adjustments

* display prompt in span card by type

* fix error in tests

* translations

* simplify span card feedback

---------

Co-authored-by: ggurdin <ggurdin@gmail.com>
2026-02-09 15:55:18 -05:00

246 lines
7.2 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:fluffychat/pangea/choreographer/igc/igc_response_model.dart';
void main() {
group('IGCResponseModel.fromJson', () {
test('passes originalInput to matches as fullText fallback', () {
final Map<String, dynamic> jsonData = {
'original_input': 'I want to know the United States',
'full_text_correction': null,
'matches': [
{
'match': {
'message': 'Grammar error',
'short_message': 'grammar',
'choices': [
{'value': 'learn about', 'type': 'bestCorrection'},
],
'offset': 10,
'length': 4,
// Note: no full_text in match - should use original_input
'type': 'grammar',
},
'status': 'open',
},
],
'user_l1': 'en',
'user_l2': 'es',
'enable_it': true,
'enable_igc': true,
};
final IGCResponseModel response = IGCResponseModel.fromJson(jsonData);
expect(response.matches.length, 1);
expect(
response.matches[0].match.fullText,
'I want to know the United States',
);
});
test('match full_text takes precedence over originalInput', () {
final Map<String, dynamic> jsonData = {
'original_input': 'Original input text',
'full_text_correction': null,
'matches': [
{
'match': {
'message': 'Grammar error',
'short_message': 'grammar',
'choices': [
{'value': 'correction', 'type': 'bestCorrection'},
],
'offset': 0,
'length': 5,
'full_text': 'Full text from span', // This should take precedence
'type': 'grammar',
},
'status': 'open',
},
],
'user_l1': 'en',
'user_l2': 'es',
'enable_it': true,
'enable_igc': true,
};
final IGCResponseModel response = IGCResponseModel.fromJson(jsonData);
expect(response.matches.length, 1);
expect(response.matches[0].match.fullText, 'Full text from span');
});
test('handles empty matches array', () {
final Map<String, dynamic> jsonData = {
'original_input': 'Clean text with no errors',
'full_text_correction': null,
'matches': <dynamic>[],
'user_l1': 'en',
'user_l2': 'es',
'enable_it': true,
'enable_igc': true,
};
final IGCResponseModel response = IGCResponseModel.fromJson(jsonData);
expect(response.matches.length, 0);
expect(response.originalInput, 'Clean text with no errors');
});
test('handles null matches', () {
final Map<String, dynamic> jsonData = {
'original_input': 'Text',
'full_text_correction': null,
'matches': null,
'user_l1': 'en',
'user_l2': 'es',
'enable_it': true,
'enable_igc': true,
};
final IGCResponseModel response = IGCResponseModel.fromJson(jsonData);
expect(response.matches.length, 0);
});
});
group('IGCResponseModel V2 format compatibility', () {
test('parses V2 response without enable_it and enable_igc', () {
// V2 response format from /choreo/grammar_v2 endpoint
final Map<String, dynamic> jsonData = {
'original_input': 'Me gusta el café',
'matches': [
{
// V2 format: flat SpanData, no "match" wrapper
'choices': [
{
'value': 'Me encanta',
'type': 'bestCorrection',
'feedback': 'Use "encantar" for expressing love',
},
],
'offset': 0,
'length': 8,
'type': 'vocabulary',
},
],
'user_l1': 'en',
'user_l2': 'es',
// Note: no enable_it, enable_igc in V2 response
};
final IGCResponseModel response = IGCResponseModel.fromJson(jsonData);
expect(response.originalInput, 'Me gusta el café');
expect(response.userL1, 'en');
expect(response.userL2, 'es');
// Should default to true when not present
expect(response.enableIT, true);
expect(response.enableIGC, true);
expect(response.matches.length, 1);
expect(response.matches[0].match.offset, 0);
expect(response.matches[0].match.length, 8);
});
test('parses V2 response with empty matches', () {
final Map<String, dynamic> jsonData = {
'original_input': 'Perfect sentence with no errors',
'matches': <dynamic>[],
'user_l1': 'en',
'user_l2': 'fr',
};
final IGCResponseModel response = IGCResponseModel.fromJson(jsonData);
expect(response.matches.length, 0);
expect(response.enableIT, true);
expect(response.enableIGC, true);
expect(response.fullTextCorrection, isNull);
});
test('parses V2 response with multiple matches', () {
final Map<String, dynamic> jsonData = {
'original_input': 'Yo soy ir a la tienda',
'matches': [
{
'choices': [
{
'value': 'voy',
'type': 'bestCorrection',
'feedback': 'Use conjugated form',
},
],
'offset': 7,
'length': 2,
'type': 'grammar',
},
{
'choices': [
{'value': 'Voy', 'type': 'bestCorrection'},
],
'offset': 0,
'length': 6,
'type': 'grammar',
},
],
'user_l1': 'en',
'user_l2': 'es',
};
final IGCResponseModel response = IGCResponseModel.fromJson(jsonData);
expect(response.matches.length, 2);
expect(response.matches[0].match.offset, 7);
expect(response.matches[1].match.offset, 0);
});
test('V1 format with explicit enable_it=false still works', () {
final Map<String, dynamic> jsonData = {
'original_input': 'Test text',
'full_text_correction': 'Corrected text',
'matches': <dynamic>[],
'user_l1': 'en',
'user_l2': 'es',
'enable_it': false,
'enable_igc': false,
};
final IGCResponseModel response = IGCResponseModel.fromJson(jsonData);
expect(response.enableIT, false);
expect(response.enableIGC, false);
expect(response.fullTextCorrection, 'Corrected text');
});
test('V2 response choice includes feedback field', () {
final Map<String, dynamic> jsonData = {
'original_input': 'Je suis alle',
'matches': [
{
'choices': [
{
'value': 'allé',
'type': 'bestCorrection',
'feedback': 'Add accent to past participle',
},
],
'offset': 8,
'length': 4,
'type': 'diacritics',
},
],
'user_l1': 'en',
'user_l2': 'fr',
};
final IGCResponseModel response = IGCResponseModel.fromJson(jsonData);
expect(response.matches.length, 1);
expect(
response.matches[0].match.bestChoice?.feedback,
'Add accent to past participle',
);
});
});
}