From 1a610212dccb27cfe6396904957a7508a86bc0f7 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Thu, 28 May 2026 11:09:21 +0300 Subject: [PATCH] feat(editor): JSON highlighting in Mongo document editor (#50) Map JSON grammar scopes in HighlighterTheme, wire mongo editor to workbench success and editor background tokens, and add format/validation tests. --- .../editor/highlighter_theme_from_querya.dart | 20 ++++- .../mongodb/mongo_document_editor.dart | 16 ++-- .../highlighter_theme_from_querya_test.dart | 9 ++ .../mongodb/mongo_document_editor_test.dart | 87 +++++++++++++++++++ 4 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 test/features/mongodb/mongo_document_editor_test.dart diff --git a/lib/core/editor/highlighter_theme_from_querya.dart b/lib/core/editor/highlighter_theme_from_querya.dart index 325a90f2..07773739 100644 --- a/lib/core/editor/highlighter_theme_from_querya.dart +++ b/lib/core/editor/highlighter_theme_from_querya.dart @@ -41,9 +41,27 @@ HighlighterTheme highlighterThemeFromQueryaEditor(QueryaEditorTheme editor) { 'settings': {'foreground': _hex(editor.string)}, }, { - 'scope': ['constant.numeric', 'number'], + 'scope': [ + 'constant.numeric', + 'constant.numeric.json', + 'number', + ], 'settings': {'foreground': _hex(editor.number)}, }, + { + 'scope': [ + 'support.type.property-name', + 'support.type.property-name.json', + ], + 'settings': {'foreground': _hex(editor.type)}, + }, + { + 'scope': [ + 'constant.language', + 'constant.language.json', + ], + 'settings': {'foreground': _hex(editor.keyword)}, + }, { 'scope': ['entity.name.function', 'support.function'], 'settings': {'foreground': _hex(editor.function)}, diff --git a/lib/features/mongodb/mongo_document_editor.dart b/lib/features/mongodb/mongo_document_editor.dart index b9d89048..3c98336b 100644 --- a/lib/features/mongodb/mongo_document_editor.dart +++ b/lib/features/mongodb/mongo_document_editor.dart @@ -5,6 +5,7 @@ import 'package:querya_desktop/core/database/mongodb_connection.dart'; import 'package:querya_desktop/core/editor/querya_code_editor.dart'; import 'package:querya_desktop/core/editor/querya_code_language.dart'; import 'package:querya_desktop/core/database/mongodb_service.dart'; +import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' as shadcn; @@ -215,6 +216,9 @@ class _MongoDocumentEditorState extends material.State { material.Widget build(material.BuildContext context) { final cs = Theme.of(context).colorScheme; final shadcnCs = shadcn.Theme.of(context).colorScheme; + final workbench = context.workbench; + final editorTheme = context.editorTheme; + final success = workbench.success; final idStr = widget.document['_id']?.toString() ?? 'New Document'; return material.Column( @@ -310,16 +314,16 @@ class _MongoDocumentEditorState extends material.State { material.Container( padding: const material.EdgeInsets.symmetric( horizontal: 16, vertical: 8), - color: const Color(0xFF4CAF50).withValues(alpha: 0.1), + color: success.withValues(alpha: 0.1), child: Row( children: [ - const material.Icon(material.Icons.check_circle_rounded, - size: 14, color: Color(0xFF4CAF50)), + material.Icon(material.Icons.check_circle_rounded, + size: 14, color: success), const Gap(8), material.Expanded( child: Text(_success!, - style: const material.TextStyle( - color: Color(0xFF4CAF50), fontSize: 12)), + style: material.TextStyle( + color: success, fontSize: 12)), ), ], ), @@ -327,7 +331,7 @@ class _MongoDocumentEditorState extends material.State { // Editor material.Expanded( child: material.Container( - color: cs.card, + color: editorTheme.background, child: QueryaCodeEditor( controller: _controller, language: QueryaCodeLanguage.json, diff --git a/test/core/editor/highlighter_theme_from_querya_test.dart b/test/core/editor/highlighter_theme_from_querya_test.dart index 1a249a37..c12ddcfb 100644 --- a/test/core/editor/highlighter_theme_from_querya_test.dart +++ b/test/core/editor/highlighter_theme_from_querya_test.dart @@ -17,4 +17,13 @@ void main() { expect(span.children, isNotNull); expect(span.children!.length, greaterThan(1)); }); + + test('highlighterThemeFromQueryaEditor produces JSON spans', () { + final theme = highlighterThemeFromQueryaEditor(QueryaTheme.darkDefault.editor); + final highlighter = Highlighter(language: 'json', theme: theme); + const sample = '{"name": "x", "count": 1, "ok": true, "nil": null}'; + final span = highlighter.highlight(sample); + expect(span.children, isNotNull); + expect(span.children!.length, greaterThan(3)); + }); } diff --git a/test/features/mongodb/mongo_document_editor_test.dart b/test/features/mongodb/mongo_document_editor_test.dart new file mode 100644 index 00000000..64f23985 --- /dev/null +++ b/test/features/mongodb/mongo_document_editor_test.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart' as material; +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/database/mongodb_connection.dart'; +import 'package:querya_desktop/core/editor/querya_code_editor.dart'; +import 'package:querya_desktop/core/editor/syntax_highlight_service.dart'; +import 'package:querya_desktop/core/theme/querya_theme.dart'; +import 'package:querya_desktop/features/mongodb/mongo_document_editor.dart'; + +import '../../support/querya_theme_test_shell.dart'; + +void main() { + setUpAll(() async { + TestWidgetsFlutterBinding.ensureInitialized(); + await SyntaxHighlightService.ensureInitialized(); + }); + + final connection = MongoConnection(id: 1, name: 'test', host: 'localhost'); + + Future pumpEditor( + WidgetTester tester, { + QueryaTheme? theme, + Map document = const {'_id': 'abc', 'a': 1}, + }) async { + await tester.pumpWidget( + queryaThemeTestShell( + data: theme ?? QueryaTheme.darkDefault, + child: material.SizedBox( + width: 800, + height: 600, + child: MongoDocumentEditor( + connection: connection, + database: 'db', + collection: 'items', + document: document, + ), + ), + ), + ); + await tester.pumpAndSettle(); + } + + testWidgets('Format pretty-prints valid JSON', (tester) async { + await pumpEditor(tester); + await tester.enterText( + find.byType(material.EditableText), + '{"a":1,"b":"x"}', + ); + await tester.pump(); + await tester.tap(find.text('Format')); + await tester.pump(); + + final editable = tester.widget( + find.byType(material.EditableText), + ); + expect(editable.controller.text, contains('\n')); + expect(editable.controller.text, contains(' "a"')); + expect(find.textContaining('Invalid JSON'), findsNothing); + }); + + testWidgets('invalid JSON shows error banner without breaking editor', (tester) async { + await pumpEditor(tester); + await tester.enterText(find.byType(material.EditableText), '{not json'); + await tester.pump(); + await tester.tap(find.text('Format')); + await tester.pump(); + + expect(find.textContaining('Invalid JSON'), findsOneWidget); + expect(find.byType(material.EditableText), findsOneWidget); + }); + + testWidgets('editor uses Querya editor background token', (tester) async { + const bg = material.Color(0xFF112233); + final theme = QueryaTheme.darkDefault.copyWith( + editor: QueryaTheme.darkDefault.editor.copyWith(background: bg), + ); + await pumpEditor(tester, theme: theme); + + final editorFinder = find.byType(QueryaCodeEditor); + final container = tester.widget( + find.ancestor( + of: editorFinder, + matching: find.byType(material.Container), + ).first, + ); + expect(container.color, bg); + }); +}