diff --git a/apps/test/core/schemas/ui_schema_test.dart b/apps/test/core/schemas/ui_schema_test.dart new file mode 100644 index 0000000..add0bc5 --- /dev/null +++ b/apps/test/core/schemas/ui_schema_test.dart @@ -0,0 +1,105 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:social_app/core/schemas/ui_schema.dart'; + +void main() { + group('ui_schema protocol stability', () { + test('UiSchemaDocument.fromJson keeps enum fallback defaults', () { + final doc = UiSchemaDocument.fromJson({ + 'version': '1.0', + 'schemaType': 'unknown_type', + 'status': 'unknown_status', + 'nodes': const [], + }); + + expect(doc.schemaType, SchemaType.toolResult); + expect(doc.status, UiStatus.info); + }); + + test('actionSpecFromJson covers known and unknown branches', () { + final navigation = actionSpecFromJson({ + 'type': 'navigation', + 'path': '/calendar/dayweek', + 'params': {'from': 'home'}, + }); + expect(navigation, isA()); + + final unknown = actionSpecFromJson({'type': 'not_supported'}); + expect(unknown, isA()); + expect((unknown as EventAction).event, 'unknown'); + }); + + test('UiNode.fromJson returns text fallback for unknown node type', () { + final node = UiNode.fromJson({'type': 'mystery'}); + + expect(node, isA()); + expect((node as UiTextNode).content, 'Unknown node type: mystery'); + }); + + test( + 'buildSuccessDocument and buildErrorDocument keep status semantics', + () { + final success = buildSuccessDocument(const [ + UiTextNode(content: 'ok'), + ], schemaType: SchemaType.agentResponse); + final error = buildErrorDocument(const [UiTextNode(content: 'bad')]); + + expect(success.status, UiStatus.success); + expect(success.schemaType, SchemaType.agentResponse); + expect(success.locale, 'zh-CN'); + + expect(error.status, UiStatus.error); + expect(error.schemaType, SchemaType.toolResult); + expect(error.version, '1.0'); + }, + ); + + test('UiSchemaDocument round-trip keeps critical fields stable', () { + final original = UiSchemaDocument( + version: '1.0', + schemaType: SchemaType.toolResult, + docId: 'doc_1', + timestamp: '2026-03-19T10:00:00Z', + locale: 'zh-CN', + status: UiStatus.success, + renderer: const RendererConfig(theme: RendererTheme.light), + meta: const DocumentMeta(requestId: 'req_1', toolId: 'tool_1'), + nodes: const [ + UiContainerNode( + direction: ContainerDirection.vertical, + children: [ + UiTextNode(content: 'hello', format: TextFormat.markdown), + ], + ), + ], + ); + + final encoded = original.toJson(); + final decoded = UiSchemaDocument.fromJson(encoded); + + expect(decoded.version, '1.0'); + expect(decoded.schemaType, SchemaType.toolResult); + expect(decoded.docId, 'doc_1'); + expect(decoded.status, UiStatus.success); + expect(decoded.renderer?.theme, RendererTheme.light); + expect(decoded.nodes, hasLength(1)); + expect(decoded.nodes.first, isA()); + }); + + test('toJson omits nullable fields as before', () { + const action = UiAction( + id: 'a1', + label: 'open', + action: NavigateAction(path: '/settings'), + ); + + final json = action.toJson(); + + expect(json['id'], 'a1'); + expect(json['label'], 'open'); + expect(json.containsKey('icon'), false); + expect(json.containsKey('style'), false); + expect(json.containsKey('confirm'), false); + expect((json['action'] as Map)['path'], '/settings'); + }); + }); +}