Repository navigation
Expand file tree
/
Copy pathProgram.cs
More file actions
1492 lines (1325 loc) · 94.8 KB
/
Copy pathProgram.cs
File metadata and controls
1492 lines (1325 loc) · 94.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using Avalonia;
using Avalonia.Controls;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Fdb;
sealed class Program
{
[STAThread]
public static async Task Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
Console.WriteLine($"[UnhandledException]: {e.ExceptionObject}");
};
TaskScheduler.UnobservedTaskException += (s, e) =>
{
Console.WriteLine($"[UnobservedTaskException]: {e.Exception}");
e.SetObserved();
};
if (args.Length > 0 && args[0] == "--verify")
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.WriteLine("=== Fdb Dynamic Layout Verification ===");
var vm = new Fdb.ViewModels.MainViewModel();
var app = BuildAvaloniaApp().SetupWithoutStarting();
var window = new Fdb.Views.MainWindow { DataContext = vm };
Console.WriteLine($"[Initial State]");
Console.WriteLine($"Sidebar: Visible={vm.Sidebar.IsVisible}, ColWidth={window.MainGrid.ColumnDefinitions[0].Width}");
Console.WriteLine($"Footer: Visible={vm.Footer.IsVisible}, RowHeight={window.CenterGrid.RowDefinitions[2].Height}");
Console.WriteLine($"Editor Row: Height={window.CenterGrid.RowDefinitions[0].Height}");
Console.WriteLine($"Center Col: Width={window.MainGrid.ColumnDefinitions[2].Width}");
// 1. Test Closing Footer
Console.WriteLine("\n[Action] Closing Footer...");
vm.ToggleFooterCommand.Execute(null);
Console.WriteLine($"Footer: Visible={vm.Footer.IsVisible}, RowHeight={window.CenterGrid.RowDefinitions[2].Height}");
Console.WriteLine($"Editor Row Height: {window.CenterGrid.RowDefinitions[0].Height} (MUST BE 1*)");
if (window.CenterGrid.RowDefinitions[2].Height.Value != 0 || window.CenterGrid.RowDefinitions[0].Height.GridUnitType != Avalonia.Controls.GridUnitType.Star)
{
throw new Exception("Footer collapse failed! Editor did not expand.");
}
// 2. Test Closing Sidebar
Console.WriteLine("\n[Action] Closing Sidebar...");
vm.ToggleSidebarCommand.Execute(null);
Console.WriteLine($"Sidebar: Visible={vm.Sidebar.IsVisible}, ColWidth={window.MainGrid.ColumnDefinitions[0].Width}");
Console.WriteLine($"Center Col Width: {window.MainGrid.ColumnDefinitions[2].Width} (MUST BE 1*)");
if (window.MainGrid.ColumnDefinitions[0].Width.Value != 0 || window.MainGrid.ColumnDefinitions[2].Width.GridUnitType != Avalonia.Controls.GridUnitType.Star)
{
throw new Exception("Sidebar collapse failed! Center area did not expand.");
}
// 2b. Test Ctrl+B Keyboard Shortcut to Toggle / Close Sidebar
Console.WriteLine("\n[Action] Verifying Ctrl+B Shortcut closes Sidebar...");
vm.Sidebar.IsVisible = true;
window.UpdateSidebarLayout();
if (!vm.Sidebar.IsVisible || window.MainGrid.ColumnDefinitions[0].Width.Value == 0)
{
throw new Exception("Sidebar failed to initialize open before Ctrl+B test!");
}
// Fire Ctrl+B into Window tunneling handler
window.RaiseEvent(new Avalonia.Input.KeyEventArgs
{
RoutedEvent = Avalonia.Input.InputElement.KeyDownEvent,
Key = Avalonia.Input.Key.B,
KeyModifiers = Avalonia.Input.KeyModifiers.Control
});
Console.WriteLine($"After Ctrl+B KeyDown: Sidebar.IsVisible={vm.Sidebar.IsVisible}, ColWidth={window.MainGrid.ColumnDefinitions[0].Width}");
if (vm.Sidebar.IsVisible || window.MainGrid.ColumnDefinitions[0].Width.Value != 0)
{
throw new Exception("Ctrl+B shortcut failed to close the Sidebar!");
}
// Fire Ctrl+B again to reopen Sidebar
window.RaiseEvent(new Avalonia.Input.KeyEventArgs
{
RoutedEvent = Avalonia.Input.InputElement.KeyDownEvent,
Key = Avalonia.Input.Key.B,
KeyModifiers = Avalonia.Input.KeyModifiers.Control
});
Console.WriteLine($"After second Ctrl+B KeyDown: Sidebar.IsVisible={vm.Sidebar.IsVisible}, ColWidth={window.MainGrid.ColumnDefinitions[0].Width}");
if (!vm.Sidebar.IsVisible || window.MainGrid.ColumnDefinitions[0].Width.Value == 0)
{
throw new Exception("Ctrl+B shortcut failed to reopen the Sidebar!");
}
Console.WriteLine("[Success] Ctrl+B shortcut verified to close and reopen Sidebar cleanly!");
// 3. Test Restoring from Window Menu
Console.WriteLine("\n[Action] Restoring Sidebar & Footer from Window Menu...");
vm.ShowSidebarCommand.Execute(null);
vm.ShowFooterCommand.Execute(null);
Console.WriteLine($"Sidebar: Visible={vm.Sidebar.IsVisible}, ColWidth={window.MainGrid.ColumnDefinitions[0].Width}");
Console.WriteLine($"Footer: Visible={vm.Footer.IsVisible}, RowHeight={window.CenterGrid.RowDefinitions[2].Height}");
// 3b. Test Maximizing Footer (Fullscreen Excel Table)
Console.WriteLine("\n[Action] Toggling Fullscreen / Maximize on Footer (Excel Table)...");
vm.ToggleFooterMaximizedCommand.Execute(null);
Console.WriteLine($"Footer Maximize State: IsMaximized={vm.Footer.IsMaximized}, FooterRowHeight={window.CenterGrid.RowDefinitions[2].Height}, EditorRowHeight={window.CenterGrid.RowDefinitions[0].Height}");
if (!vm.Footer.IsMaximized || window.CenterGrid.RowDefinitions[2].Height.GridUnitType != Avalonia.Controls.GridUnitType.Star || window.CenterGrid.RowDefinitions[0].Height.Value != 0)
{
throw new Exception("Footer fullscreen failed! Footer did not take 1* or Editor row is not 0.");
}
Console.WriteLine("[Action] Restoring Footer from Fullscreen...");
vm.ToggleFooterMaximizedCommand.Execute(null);
Console.WriteLine($"Footer Restored: IsMaximized={vm.Footer.IsMaximized}, FooterRowHeight={window.CenterGrid.RowDefinitions[2].Height}, EditorRowHeight={window.CenterGrid.RowDefinitions[0].Height}");
if (vm.Footer.IsMaximized || window.CenterGrid.RowDefinitions[2].Height.GridUnitType != Avalonia.Controls.GridUnitType.Pixel || window.CenterGrid.RowDefinitions[0].Height.GridUnitType != Avalonia.Controls.GridUnitType.Star)
{
throw new Exception("Footer restore failed! Footer is not pixel height or Editor is not 1*.");
}
// 4. Test Table Components Data
Console.WriteLine("\n[Action] Verifying Table Components...");
Console.WriteLine($"QueryResultTable Initial Rows: {vm.SampleQueryResult.Rows.Count} (Clean initial state, 0 default mock rows).");
if (vm.SampleQueryResult.Rows.Count != 0)
{
throw new Exception($"Expected clean initial table (0 rows), but got {vm.SampleQueryResult.Rows.Count}");
}
if (vm.Footer.Tabs.Count != 0)
{
throw new Exception($"Expected clean initial footer tabs (0 tabs), but got {vm.Footer.Tabs.Count}");
}
Console.WriteLine($"SchemaPropertiesTable Columns: {vm.SampleSchema.Columns.Count} columns loaded.");
var tableComp = new Fdb.Components.QueryResultTableComponent { DataContext = vm.SampleQueryResult };
var dataGrid = tableComp.FindControl<Avalonia.Controls.DataGrid>("ResultsDataGrid");
Console.WriteLine($"DataGrid Columns Count: {dataGrid?.Columns.Count ?? 0}");
Console.WriteLine($"DataGrid RowHeight: {dataGrid?.RowHeight}px (Compact height)");
if (dataGrid?.RowHeight != 25)
{
throw new Exception($"Expected compact DataGrid RowHeight=25, but got {dataGrid?.RowHeight}");
}
var testRow = new Fdb.ViewModels.QueryResultRow { RowIndex = 1, Id = 101, Name = "Alice Johnson" };
var col0 = dataGrid?.Columns[0] as Avalonia.Controls.DataGridTemplateColumn;
var cellContent = col0?.CellTemplate?.Build(testRow) as Avalonia.Controls.Grid;
if (cellContent != null) cellContent.DataContext = testRow;
var textBlock = cellContent?.Children[0] as Avalonia.Controls.TextBlock;
Console.WriteLine($"Cell 0 TextBlock Text: '{textBlock?.Text}', DataContext: {textBlock?.DataContext}");
if (textBlock?.Text != "1")
{
throw new Exception($"Expected row index '1', but got '{textBlock?.Text}'");
}
tableComp.DataContext = null;
// 5. Test Modal Dialogs Lifecycle & Triggers
Console.WriteLine("\n[Action] Verifying Modal Dialogs Lifecycle...");
Console.WriteLine($"ConnectModal initially open: {vm.ConnectModal.IsOpen}");
var navbarComp = window.FindControl<Fdb.Components.NavbarComponent>("MainNavbar");
var connectBtn = navbarComp?.FindControl<Avalonia.Controls.Button>("ConnectButton");
Console.WriteLine($"ConnectButton found: {connectBtn != null}, Command: {connectBtn?.Command}");
connectBtn?.Command?.Execute(null);
Console.WriteLine($"After ConnectButton click: ConnectModal.IsOpen = {vm.ConnectModal.IsOpen}");
if (!vm.ConnectModal.IsOpen)
{
throw new Exception("ConnectButton failed to open ConnectModal!");
}
vm.ConnectModal.TestConnectionCommand.Execute(null);
Console.WriteLine($"ConnectModal Status: {vm.ConnectModal.StatusMessage}");
vm.ConnectModal.CloseCommand.Execute(null);
Console.WriteLine($"ConnectModal: IsOpen = {vm.ConnectModal.IsOpen} (Closed)");
// Test Disconnect Command triggering ConfirmModal
vm.DisconnectCommand.Execute(null);
Console.WriteLine($"ConfirmModal opened by Disconnect: {vm.ConfirmModal.IsOpen}, Target = {vm.ConfirmModal.TargetName}");
if (!vm.ConfirmModal.IsOpen)
{
throw new Exception("DisconnectCommand failed to open ConfirmModal!");
}
vm.ConfirmModal.CancelCommand.Execute(null);
Console.WriteLine($"ConfirmModal: IsOpen = {vm.ConfirmModal.IsOpen} (Closed)");
// Test Export Modal
vm.ExportModal.OpenCommand.Execute(null);
Console.WriteLine($"ExportModal: IsOpen = {vm.ExportModal.IsOpen}, Format = {vm.ExportModal.SelectedFormat}");
vm.ExportModal.CloseCommand.Execute(null);
Console.WriteLine($"ExportModal: IsOpen = {vm.ExportModal.IsOpen} (Closed)");
// Test Settings Modal (Appearance & Theme Switching)
Console.WriteLine("\n[Action] Verifying Settings Modal & Theme Switching...");
Console.WriteLine($"SettingsModal initially open: {vm.SettingsModal.IsOpen}");
await vm.SettingsModal.OpenAsync();
Console.WriteLine($"After OpenAsync: SettingsModal.IsOpen = {vm.SettingsModal.IsOpen}, SelectedTheme = {vm.SettingsModal.SelectedTheme}");
if (!vm.SettingsModal.IsOpen) throw new Exception("SettingsModal failed to open!");
// Test switching to Dark theme
Console.WriteLine("[Action] Switching to Dark theme...");
vm.SettingsModal.SelectTheme("Dark");
Console.WriteLine($"Theme applied: CurrentTheme={Fdb.Services.ThemeService.Instance.CurrentTheme}, IsDark={vm.SettingsModal.IsDarkTheme}");
if (!vm.SettingsModal.IsDarkTheme || Fdb.Services.ThemeService.Instance.CurrentTheme != "Dark")
{
throw new Exception("Dark theme selection failed!");
}
await vm.SettingsModal.SaveAsync();
Console.WriteLine($"After SaveAsync: SettingsModal.IsOpen = {vm.SettingsModal.IsOpen}");
string persistedTheme = await Fdb.Services.DatabaseStorageService.Instance.GetThemeAsync();
Console.WriteLine($"SQLite Persisted Theme: '{persistedTheme}'");
if (persistedTheme != "Dark") throw new Exception("Failed to persist Dark theme to SQLite!");
// Test switching to System theme
await vm.SettingsModal.OpenAsync();
vm.SettingsModal.SelectTheme("System");
if (!vm.SettingsModal.IsSystemTheme || Fdb.Services.ThemeService.Instance.CurrentTheme != "System")
{
throw new Exception("System theme selection failed!");
}
await vm.SettingsModal.SaveAsync();
string persistedSystemTheme = await Fdb.Services.DatabaseStorageService.Instance.GetThemeAsync();
Console.WriteLine($"SQLite Persisted System Theme: '{persistedSystemTheme}'");
if (persistedSystemTheme != "System") throw new Exception("Failed to persist System theme to SQLite!");
// Restore back to Light theme
await vm.SettingsModal.OpenAsync();
vm.SettingsModal.SelectTheme("Light");
await vm.SettingsModal.SaveAsync();
string restoredTheme = await Fdb.Services.DatabaseStorageService.Instance.GetThemeAsync();
Console.WriteLine($"Restored Theme in SQLite: '{restoredTheme}'");
if (restoredTheme != "Light") throw new Exception("Failed to restore Light theme in SQLite!");
// Test Keyword Color Customization & High Contrast Adaptation across Themes
Console.WriteLine("\n[Action] Verifying Keyword Syntax Color Customization & Contrast Adaptation...");
await vm.SettingsModal.OpenAsync();
Console.WriteLine($"Initial Keyword Color: '{vm.SettingsModal.SelectedKeywordColor}'");
// Select 'Green' (Emerald / Mint)
vm.SettingsModal.SelectKeywordColor("Green");
Console.WriteLine($"Selected Green Keyword Color: IsGreen={vm.SettingsModal.IsGreenKeyword}");
if (!vm.SettingsModal.IsGreenKeyword) throw new Exception("Green keyword selection failed in ViewModel!");
// In Light theme, Green should be emerald (#059669)
var lightBrush = Fdb.Services.ThemeService.Instance.CurrentKeywordBrush as Avalonia.Media.ISolidColorBrush;
Console.WriteLine($"Light Theme Green Keyword Brush: {lightBrush?.Color}");
if (lightBrush?.Color != Avalonia.Media.Color.Parse("#059669"))
{
throw new Exception($"Expected Light Theme Green Color #059669, got {lightBrush?.Color}");
}
// Switch to Dark Theme: Green keyword should automatically adapt to bright mint (#34D399) for high contrast!
vm.SettingsModal.SelectTheme("Dark");
var darkBrush = Fdb.Services.ThemeService.Instance.CurrentKeywordBrush as Avalonia.Media.ISolidColorBrush;
Console.WriteLine($"Dark Theme Green Keyword Brush (Auto Contrast): {darkBrush?.Color}");
if (darkBrush?.Color != Avalonia.Media.Color.Parse("#34D399"))
{
throw new Exception($"Expected Dark Theme Mint Color #34D399, got {darkBrush?.Color}");
}
// Save Green Keyword Color & Dark Theme to SQLite
await vm.SettingsModal.SaveAsync();
string persistedKeywordColor = await Fdb.Services.DatabaseStorageService.Instance.GetKeywordColorAsync();
Console.WriteLine($"SQLite Persisted Keyword Color: '{persistedKeywordColor}'");
if (persistedKeywordColor != "Green") throw new Exception("Failed to persist Green Keyword Color to SQLite!");
// Revert back to Blue Keyword Color and Light Theme
await vm.SettingsModal.OpenAsync();
vm.SettingsModal.SelectKeywordColor("Blue");
vm.SettingsModal.SelectTheme("Light");
await vm.SettingsModal.SaveAsync();
string restoredKeywordColor = await Fdb.Services.DatabaseStorageService.Instance.GetKeywordColorAsync();
Console.WriteLine($"Restored Keyword Color in SQLite: '{restoredKeywordColor}'");
if (restoredKeywordColor != "Blue") throw new Exception("Failed to restore Blue Keyword Color to SQLite!");
// 5b. Test Panel Customization, Color Maker, Button/Border/Sidebar & SQLite Persistence
Console.WriteLine("\n[Action] Verifying Color Maker ('رنگساز'), Buttons, Borders & Multi-Part Customization...");
await vm.SettingsModal.OpenAsync();
vm.SettingsModal.SelectCategory("Customization");
Console.WriteLine($"Settings Category: Selected='{vm.SettingsModal.SelectedCategory}', IsCustomization={vm.SettingsModal.IsCustomizationCategory}");
if (!vm.SettingsModal.IsCustomizationCategory) throw new Exception("Customization category selection failed!");
// Test Color Maker Hex and RGB conversions
vm.SettingsModal.SetColorMakerHex("#10B981");
Console.WriteLine($"Color Maker parsed '#10B981': Hex={vm.SettingsModal.ColorMakerHex}, R={vm.SettingsModal.ColorMakerR}, G={vm.SettingsModal.ColorMakerG}, B={vm.SettingsModal.ColorMakerB}");
if (vm.SettingsModal.ColorMakerR != 16 || vm.SettingsModal.ColorMakerG != 185 || vm.SettingsModal.ColorMakerB != 129)
{
throw new Exception("Color Maker hex to RGB parsing failed!");
}
// Test Color Maker Apply to Target
vm.SettingsModal.ColorMakerSelectedTarget = "Buttons: Accent / Action";
vm.SettingsModal.ApplyColorMakerToTarget();
Console.WriteLine($"Applied Color Maker to Button Accent: ButtonAccentColor={vm.SettingsModal.ButtonAccentColor}");
if (vm.SettingsModal.ButtonAccentColor != "#10B981") throw new Exception("Color Maker failed to apply to Button Accent!");
// Customize Buttons, Borders, Sidebar parts, Editor
vm.SettingsModal.SelectButtonAccentColor("#38BDF8");
vm.SettingsModal.SelectButtonBackgroundColor("#1E293B");
vm.SettingsModal.SelectButtonTextColor("#F8FAFC");
vm.SettingsModal.SelectButtonBorderColor("#334155");
vm.SettingsModal.SelectBorderColor("#334155");
vm.SettingsModal.SelectSplitterColor("#38BDF8");
vm.SettingsModal.SelectSidebarBackground("#141E2E");
vm.SettingsModal.SelectSidebarHeaderBackground("#1E293B");
vm.SettingsModal.SelectSidebarItemHoverBackground("#334155");
vm.SettingsModal.SelectSidebarTextColor("#F8FAFC");
vm.SettingsModal.SelectSidebarBorderColor("#334155");
vm.SettingsModal.SelectEditorBackground("#1E293B");
vm.SettingsModal.SelectEditorForeground("#34D399");
vm.SettingsModal.SelectTableBackground("#1E293B");
vm.SettingsModal.EditorFontSize = 15.0;
vm.SettingsModal.EditorShowLineNumbers = true;
await vm.SettingsModal.SaveAsync();
var savedCustom = await Fdb.Services.DatabaseStorageService.Instance.GetPanelCustomizationAsync();
Console.WriteLine($"SQLite Saved Panel Customization: ButtonAccent={savedCustom.ButtonAccentColor}, BorderColor={savedCustom.BorderColor}, SidebarHeader={savedCustom.SidebarHeaderBackground}, EditorBg={savedCustom.EditorBackground}, FontSize={savedCustom.EditorFontSize}");
if (savedCustom.ButtonAccentColor != "#38BDF8" || savedCustom.BorderColor != "#334155" || savedCustom.SidebarHeaderBackground != "#1E293B" || savedCustom.EditorBackground != "#1E293B" || savedCustom.EditorFontSize != 15.0)
{
throw new Exception("Expanded panel customization failed to persist to SQLite!");
}
// Test Reset Panel Customization
await vm.SettingsModal.OpenAsync();
vm.SettingsModal.ResetPanelCustomization();
await vm.SettingsModal.SaveAsync();
var resetCustom = await Fdb.Services.DatabaseStorageService.Instance.GetPanelCustomizationAsync();
Console.WriteLine($"SQLite Restored Panel Customization: ButtonAccent={resetCustom.ButtonAccentColor}, BorderColor={resetCustom.BorderColor}, EditorBg={resetCustom.EditorBackground}, FontSize={resetCustom.EditorFontSize}");
if (resetCustom.ButtonAccentColor != "Default" || resetCustom.BorderColor != "Default" || resetCustom.EditorBackground != "Default" || resetCustom.EditorFontSize != 13.0)
{
throw new Exception("Reset panel customization failed!");
}
Console.WriteLine("[Success] Color Maker, Button/Border/Sidebar customization, and SQLite persistence verified!");
// 5c. Test Dynamic Shortcuts System (Read, Customize, and Reset from SQLite)
Console.WriteLine("\n[Action] Verifying Dynamic Shortcuts System & SQLite Persistence...");
await vm.SettingsModal.OpenAsync();
vm.SettingsModal.SelectCategory("Shortcuts");
Console.WriteLine($"Settings Category: Selected='{vm.SettingsModal.SelectedCategory}', IsShortcuts={vm.SettingsModal.IsShortcutsCategory}");
if (!vm.SettingsModal.IsShortcutsCategory) throw new Exception("Shortcuts category selection failed!");
Console.WriteLine($"Loaded Shortcuts Count: {vm.SettingsModal.Shortcuts.Count}");
if (vm.SettingsModal.Shortcuts.Count == 0) throw new Exception("No shortcuts loaded from SQLite!");
var runQueryShortcut = vm.SettingsModal.Shortcuts.FirstOrDefault(s => s.Id == Fdb.Services.ShortcutActions.RunQuery);
if (runQueryShortcut == null) throw new Exception("RunQuery shortcut definition missing!");
Console.WriteLine($"Initial RunQuery Shortcut: '{runQueryShortcut.CurrentGesture}' (Default: '{runQueryShortcut.DefaultGesture}')");
// Customize RunQuery shortcut to 'F9'
vm.SettingsModal.StartEditShortcut(runQueryShortcut);
runQueryShortcut.EditingGesture = "F9";
vm.SettingsModal.ApplyEditShortcut(runQueryShortcut);
Console.WriteLine($"Customized RunQuery Shortcut: '{runQueryShortcut.CurrentGesture}', IsModified={runQueryShortcut.IsModified}");
if (runQueryShortcut.CurrentGesture != "F9" || !runQueryShortcut.IsModified)
{
throw new Exception("Customizing shortcut in ViewModel failed!");
}
await vm.SettingsModal.SaveAsync();
// Verify in SQLite
var dbShortcuts = await Fdb.Services.DatabaseStorageService.Instance.GetShortcutsFromDbAsync();
var dbRunQuery = dbShortcuts.FirstOrDefault(s => s.Id == Fdb.Services.ShortcutActions.RunQuery);
Console.WriteLine($"SQLite Persisted RunQuery Shortcut: '{dbRunQuery?.KeyGesture}'");
if (dbRunQuery?.KeyGesture != "F9")
{
throw new Exception("Customized shortcut failed to persist to SQLite!");
}
// Verify ShortcutService key matching engine
bool isF9Match = Fdb.Services.ShortcutService.Instance.IsMatch(Fdb.Services.ShortcutActions.RunQuery,
new Avalonia.Input.KeyEventArgs { Key = Avalonia.Input.Key.F9, KeyModifiers = Avalonia.Input.KeyModifiers.None });
Console.WriteLine($"ShortcutService matches 'F9' for RunQuery: {isF9Match}");
if (!isF9Match) throw new Exception("ShortcutService key matching engine failed for customized 'F9'!");
// Test Reset Shortcut to Default
await vm.SettingsModal.OpenAsync();
var runQueryToReset = vm.SettingsModal.Shortcuts.FirstOrDefault(s => s.Id == Fdb.Services.ShortcutActions.RunQuery);
vm.SettingsModal.ResetShortcut(runQueryToReset!);
await vm.SettingsModal.SaveAsync();
var dbResetShortcuts = await Fdb.Services.DatabaseStorageService.Instance.GetShortcutsFromDbAsync();
var dbResetRunQuery = dbResetShortcuts.FirstOrDefault(s => s.Id == Fdb.Services.ShortcutActions.RunQuery);
Console.WriteLine($"SQLite Restored RunQuery Shortcut: '{dbResetRunQuery?.KeyGesture}'");
if (dbResetRunQuery?.KeyGesture != "F5")
{
throw new Exception("Reset shortcut failed to restore default 'F5' in SQLite!");
}
Console.WriteLine("[Success] Dynamic shortcuts system, live customization & SQLite persistence verified!");
// 5d. Test Query Results Table Customization (RowHeight, HeaderHeight, Colors, Presets & SQLite Persistence)
Console.WriteLine("\n[Action] Verifying Results Table Customization & SQLite Persistence...");
await vm.SettingsModal.OpenAsync();
vm.SettingsModal.SelectCategory("Table");
Console.WriteLine($"Settings Category: Selected='{vm.SettingsModal.SelectedCategory}', IsTable={vm.SettingsModal.IsTableCategory}");
if (!vm.SettingsModal.IsTableCategory) throw new Exception("Table category selection failed!");
// Test applying custom row height, colors, font sizes
vm.SettingsModal.SetTableRowHeight(28.0);
vm.SettingsModal.SetTableHeaderHeight(30.0);
vm.SettingsModal.SetTableGutterWidth(64.0);
vm.SettingsModal.SetTableCellFontSize(12.0);
vm.SettingsModal.SelectTableBackground("#1E293B");
vm.SettingsModal.SelectTableAlternatingRowColor("#141E2E");
vm.SettingsModal.SelectTableCellTextColor("#F8FAFC");
vm.SettingsModal.SelectTableGridLinesColor("#334155");
vm.SettingsModal.SelectTableSelectionColor("#1E3A5F");
await vm.SettingsModal.SaveAsync();
var savedTableCustom = await Fdb.Services.DatabaseStorageService.Instance.GetTableCustomizationAsync();
Console.WriteLine($"SQLite Saved Table Customization: RowHeight={savedTableCustom.RowHeight}, HeaderHeight={savedTableCustom.HeaderHeight}, GutterWidth={savedTableCustom.GutterWidth}, FontSize={savedTableCustom.FontSize}, TableBg={savedTableCustom.TableBackground}, AltRowBg={savedTableCustom.AlternatingRowColor}");
if (savedTableCustom.RowHeight != 28.0 || savedTableCustom.HeaderHeight != 30.0 || savedTableCustom.GutterWidth != 64.0 || savedTableCustom.FontSize != 12.0 || savedTableCustom.TableBackground != "#1E293B" || savedTableCustom.AlternatingRowColor != "#141E2E")
{
throw new Exception("Table customization failed to persist to SQLite!");
}
// Verify live dynamic update in ThemeService
Console.WriteLine($"ThemeService CurrentTableCustomization: RowHeight={Fdb.Services.ThemeService.Instance.CurrentTableCustomization.RowHeight}, FontSize={Fdb.Services.ThemeService.Instance.CurrentTableCustomization.FontSize}");
if (Fdb.Services.ThemeService.Instance.CurrentTableCustomization.RowHeight != 28.0 || Fdb.Services.ThemeService.Instance.CurrentTableCustomization.FontSize != 12.0)
{
throw new Exception("ThemeService failed to dynamically update CurrentTableCustomization!");
}
// Test Reset Table Customization
await vm.SettingsModal.OpenAsync();
vm.SettingsModal.ResetTableCustomization();
await vm.SettingsModal.SaveAsync();
var resetTableCustom = await Fdb.Services.DatabaseStorageService.Instance.GetTableCustomizationAsync();
Console.WriteLine($"SQLite Restored Table Customization: RowHeight={resetTableCustom.RowHeight}, FontSize={resetTableCustom.FontSize}, TableBg={resetTableCustom.TableBackground}");
if (resetTableCustom.RowHeight != 25.0 || resetTableCustom.FontSize != 11.0 || resetTableCustom.TableBackground != "Default")
{
throw new Exception("Reset table customization failed to restore defaults!");
}
Console.WriteLine("[Success] Results table customization, sizing, presets and SQLite persistence verified!");
// 6. Test Editor Tab Switching & Active State
Console.WriteLine("\n[Action] Verifying Editor Tab Switching & State...");
Console.WriteLine($"Initial Selected Tab: {vm.Editor.SelectedTab?.Title}, IsActive={vm.Editor.SelectedTab?.IsActive}");
vm.Editor.SelectTab(vm.Editor.Tabs[1]);
Console.WriteLine($"Switched Selected Tab: {vm.Editor.SelectedTab?.Title}, IsActive={vm.Editor.SelectedTab?.IsActive}");
if (!vm.Editor.Tabs[1].IsActive || vm.Editor.Tabs[0].IsActive)
{
throw new Exception("Editor tab activation failed!");
}
var editorComp = new Fdb.Components.EditorComponent { DataContext = vm.Editor };
var textEditor = editorComp.FindControl<AvaloniaEdit.TextEditor>("CodeEditor");
Console.WriteLine($"TextEditor Document Text Length: {textEditor?.Document?.TextLength}");
// Test per-tab document undo/redo integrity
vm.Editor.SelectTab(vm.Editor.Tabs[0]);
var tab0Doc = vm.Editor.Tabs[0].Document;
int initialLen = tab0Doc.TextLength;
tab0Doc.Insert(0, "-- New Line Added\n");
Console.WriteLine($"Tab 0 modified. CanUndo: {textEditor?.CanUndo}");
if (textEditor?.CanUndo != true)
{
throw new Exception("Editor CanUndo should be true after text edit!");
}
// Switch to tab 1
vm.Editor.SelectTab(vm.Editor.Tabs[1]);
Console.WriteLine($"Switched to Tab 1. Document matches: {textEditor?.Document == vm.Editor.Tabs[1].Document}");
// Switch back to tab 0
vm.Editor.SelectTab(vm.Editor.Tabs[0]);
Console.WriteLine($"Switched back to Tab 0. CanUndo preserved: {textEditor?.CanUndo}");
if (textEditor?.CanUndo != true)
{
throw new Exception("Editor CanUndo should be preserved across tab switches!");
}
// Perform Undo (Ctrl+Z)
textEditor.Undo();
Console.WriteLine($"Undo executed. Length restored: {tab0Doc.TextLength == initialLen}");
if (tab0Doc.TextLength != initialLen)
{
throw new Exception("Undo failed to revert document to initial length!");
}
// Perform Redo (Ctrl+Y)
textEditor.Redo();
Console.WriteLine($"Redo executed. CanUndo: {textEditor.CanUndo}");
textEditor.Undo(); // Revert back for clean state
// 7. Test SQL Keywords Service & Regex
Console.WriteLine("\n[Action] Verifying SQL Keywords Service...");
var keywordsService = Fdb.Services.SqlKeywordsService.Instance;
Console.WriteLine($"Loaded SQL Keywords: {keywordsService.Keywords.Count} keywords, {keywordsService.Snippets.Count} snippets.");
var regex = keywordsService.KeywordRegex;
bool selectMatches = regex != null && regex.IsMatch("SELECT * FROM dbo.Users");
Console.WriteLine($"Regex Matches 'SELECT * FROM': {selectMatches}");
if (!selectMatches)
{
throw new Exception("SQL Keywords regex matching failed!");
}
// Test typing in editor
Console.WriteLine("\n[Action] Testing typing 'se' in TextEditor...");
// Test typing in editor & Zoom shortcuts
Console.WriteLine("\n[Action] Testing typing 'se' in TextEditor & Zoom Shortcuts...");
var editorForTest = new Fdb.Components.EditorComponent { DataContext = vm.Editor };
var te = editorForTest.FindControl<AvaloniaEdit.TextEditor>("CodeEditor")!;
te.Document.Text = "";
te.CaretOffset = 0;
te.Document.Insert(0, "s");
te.CaretOffset = 1;
string word1 = editorForTest.GetWordBeforeOffset(1);
Console.WriteLine($"Word after typing 's': '{word1}'");
if (word1 != "s") throw new Exception("Failed to extract single-character word prefix!");
te.Document.Insert(1, "e");
te.CaretOffset = 2;
string word2 = editorForTest.GetWordBeforeOffset(2);
Console.WriteLine($"Word after typing 'se': '{word2}'");
if (word2 != "se") throw new Exception("Failed to extract 'se' word prefix!");
// Test Zoom In / Out / Reset
Console.WriteLine($"Initial Editor FontSize: {te.FontSize}");
if (te.FontSize != 13) throw new Exception("Default font size should be 13!");
editorForTest.ZoomIn();
Console.WriteLine($"After ZoomIn (Ctrl++): FontSize={te.FontSize}");
if (te.FontSize != 14) throw new Exception("ZoomIn failed to increase font size!");
editorForTest.ZoomOut();
editorForTest.ZoomOut();
Console.WriteLine($"After ZoomOut twice (Ctrl+-): FontSize={te.FontSize}");
if (te.FontSize != 12) throw new Exception("ZoomOut failed to decrease font size!");
editorForTest.ResetZoom();
Console.WriteLine($"After ResetZoom (Ctrl+0): FontSize={te.FontSize}");
if (te.FontSize != 13) throw new Exception("ResetZoom failed to restore font size to 13!");
// Test CompletionData creation and replacement
Console.WriteLine("\n[Action] Testing CompletionData instantiation and insertion...");
var kwData = new Fdb.Services.SqlCompletionData("SELECT", "SQL Keyword: SELECT", 10.0, false);
var snipData = new Fdb.Services.SqlCompletionData("SELECT * FROM", "Select all columns", 8.0, true, "SELECT * FROM ");
Console.WriteLine($"kwData: Text={kwData.Text}, IsSnippet={kwData.IsSnippet}, Category={kwData.Category}");
Console.WriteLine($"snipData: Text={snipData.Text}, IsSnippet={snipData.IsSnippet}, InsertionText='{snipData.InsertionText}'");
// Execute completion on editor
kwData.Complete(te.TextArea, new AvaloniaEdit.Document.TextSegment { StartOffset = 0, Length = 2 }, EventArgs.Empty);
Console.WriteLine($"Editor text after completing 'SELECT': '{te.Document.Text}'");
if (te.Document.Text != "SELECT") throw new Exception("CompletionData failed to insert 'SELECT'!");
// Test In-Place IntelliSense Overlay Component
Console.WriteLine("\n[Action] Verifying Modular SqlIntelliSenseOverlayComponent on EditorComponent...");
te.Document.Text = "";
te.CaretOffset = 0;
te.Document.Insert(0, "S");
te.CaretOffset = 1;
editorForTest.UpdateAndShowCompletion("S", showAll: false);
var overlay = editorForTest.FindControl<Fdb.Components.SqlIntelliSenseOverlayComponent>("IntelliSenseOverlay")!;
Console.WriteLine($"After typing 'S': Overlay.IsVisible = {overlay.IsVisible}, ItemCount = {overlay.ItemCount}");
if (!overlay.IsVisible || overlay.ItemCount == 0)
{
throw new Exception("SqlIntelliSenseOverlayComponent should be visible with items after typing 'S'!");
}
var topItem = overlay.SelectedItem;
Console.WriteLine($"Top selected item for 'S': Text='{topItem?.Text}', Category='{topItem?.Category}'");
if (topItem?.Text != "SELECT")
{
throw new Exception($"Expected top selected item to be 'SELECT', but got '{topItem?.Text}'");
}
// Test typing 'E' ('SE')
te.Document.Insert(1, "E");
te.CaretOffset = 2;
editorForTest.UpdateAndShowCompletion("SE", showAll: false);
var seTopItem = overlay.SelectedItem;
Console.WriteLine($"After typing 'SE': Overlay.IsVisible = {overlay.IsVisible}, TopItem='{seTopItem?.Text}'");
if (seTopItem?.Text != "SELECT")
{
throw new Exception($"Expected top selected item for 'SE' to be 'SELECT', but got '{seTopItem?.Text}'");
}
// Test Commit (Tab / Enter)
editorForTest.CommitSelectedCompletion();
Console.WriteLine($"After Commit: Document.Text='{te.Document.Text}', Overlay.IsVisible={overlay.IsVisible}");
if (te.Document.Text != "SELECT " || overlay.IsVisible)
{
throw new Exception($"Commit failed! Expected 'SELECT ' and overlay hidden, got '{te.Document.Text}' and IsVisible={overlay.IsVisible}");
}
// Test MainEditor in Window
Console.WriteLine("\n[Action] Verifying MainEditor in Window visual tree...");
var mainEditor = window.FindControl<Fdb.Components.EditorComponent>("MainEditor");
Console.WriteLine($"MainEditor found in window: {mainEditor != null}");
var mainCodeEditor = mainEditor?.FindControl<AvaloniaEdit.TextEditor>("CodeEditor");
Console.WriteLine($"MainCodeEditor found: {mainCodeEditor != null}");
if (mainEditor != null && mainCodeEditor != null)
{
mainCodeEditor.Document = new AvaloniaEdit.Document.TextDocument();
mainCodeEditor.Document.Text = "";
mainCodeEditor.CaretOffset = 0;
// Simulate typing 's'
var textArgs = new Avalonia.Input.TextInputEventArgs
{
Text = "s",
RoutedEvent = Avalonia.Input.InputElement.TextInputEvent
};
mainCodeEditor.TextArea.RaiseEvent(textArgs);
var winOverlay = mainEditor.FindControl<Fdb.Components.SqlIntelliSenseOverlayComponent>("IntelliSenseOverlay")!;
Console.WriteLine($"Window MainEditor after typing 's': Overlay.IsVisible={winOverlay.IsVisible}, ItemCount={winOverlay.ItemCount}");
var winTopItem = winOverlay.SelectedItem;
Console.WriteLine($"Window MainEditor TopItem: '{winTopItem?.Text}'");
if (!winOverlay.IsVisible || winTopItem?.Text != "SELECT")
{
throw new Exception("Window MainEditor failed to suggest 'SELECT' on typing 's'!");
}
// Simulate Enter commit
mainEditor.CommitSelectedCompletion();
Console.WriteLine($"Window MainEditor after commit: Document.Text='{mainCodeEditor.Document.Text}'");
if (mainCodeEditor.Document.Text != "SELECT ")
{
throw new Exception("Window MainEditor failed to commit 'SELECT '!");
}
}
window.DataContext = null;
// 8. Test Zero Emojis / All Vector Icons
Console.WriteLine("\n[Action] Verifying Zero Emojis / Native Vector Icons...");
var emojiRegex = new System.Text.RegularExpressions.Regex(@"[\uD83C-\uDBFF\uDC00-\uDFFF\u2600-\u27BF]");
int emojiCount = 0;
foreach (var file in System.IO.Directory.GetFiles("Components", "*.axaml", System.IO.SearchOption.AllDirectories))
{
var content = System.IO.File.ReadAllText(file);
var matches = emojiRegex.Matches(content);
emojiCount += matches.Count;
}
Console.WriteLine($"Total AXAML Emojis Remaining: {emojiCount} (All replaced with Fluent FASymbolIcon & PathIcon)");
if (emojiCount > 0)
{
throw new Exception("Emojis found in AXAML files!");
}
// 9. Test Splash Screen Lifecycle & SQLite Startup Execution
Console.WriteLine("\n[Action] Verifying Splash Screen Window & SQL Startup Execution...");
var splashWindow = new Fdb.Views.SplashScreenWindow();
Console.WriteLine($"Splash initial status: '{splashWindow.ViewModel.StatusText}', Progress: {splashWindow.ViewModel.ProgressValue}%");
await splashWindow.RunStartupSequenceAsync().ConfigureAwait(false);
Console.WriteLine($"Splash completed status: '{splashWindow.ViewModel.StatusText}', Progress: {splashWindow.ViewModel.ProgressValue}%");
if (splashWindow.ViewModel.ProgressValue != 100)
{
throw new Exception("Splash sequence failed to reach 100%!");
}
splashWindow.Dismiss();
// 10. Test SQLite Local Storage Service & Dialect Keywords
Console.WriteLine("\n[Action] Verifying SQLite Local Storage Service & Multi-Dialect Keywords...");
var storageService = Fdb.Services.DatabaseStorageService.Instance;
await storageService.InitializeAsync();
Console.WriteLine($"SQLite DatabasePath: '{storageService.DatabasePath}'");
if (storageService.DatabasePath.Contains("bin\\Debug") || storageService.DatabasePath.Contains("bin/Debug"))
{
throw new Exception("SQLite database path should not be inside binary output directory!");
}
// Verify DatabaseInitialized flag in AppSettings
string isInit = await storageService.GetSettingAsync("DatabaseInitialized") ?? "";
Console.WriteLine($"DatabaseInitialized setting in AppSettings: '{isInit}'");
if (isInit != "true")
{
throw new Exception("DatabaseInitialized flag missing in AppSettings after init!");
}
// Test subsequent run idempotency: re-running InitializeAsync should NOT re-seed, duplicate or overwrite
int kwCountBefore = (await storageService.GetKeywordsAsync("MSSQL")).Count;
await storageService.InitializeAsync();
int kwCountAfter = (await storageService.GetKeywordsAsync("MSSQL")).Count;
if (kwCountBefore != kwCountAfter)
{
throw new Exception($"Re-running InitializeAsync altered keyword count! Before={kwCountBefore}, After={kwCountAfter}");
}
Console.WriteLine($"Subsequent launch idempotency verified: Init skipped, keywords preserved ({kwCountAfter})");
var dialects = await storageService.GetSupportedDialectsAsync();
Console.WriteLine($"SQLite Supported Dialects: {string.Join(", ", dialects)}");
if (!dialects.Contains("MSSQL") || !dialects.Contains("PostgreSQL") || !dialects.Contains("MySQL") || !dialects.Contains("SQLite"))
{
throw new Exception("Supported dialects missing expected database engines in SQLite!");
}
var mssqlKeywords = await storageService.GetKeywordsAsync("MSSQL");
var pgKeywords = await storageService.GetKeywordsAsync("PostgreSQL");
var mysqlKeywords = await storageService.GetKeywordsAsync("MySQL");
var sqliteKeywords = await storageService.GetKeywordsAsync("SQLite");
Console.WriteLine($"Keywords count: MSSQL={mssqlKeywords.Count}, PostgreSQL={pgKeywords.Count}, MySQL={mysqlKeywords.Count}, SQLite={sqliteKeywords.Count}");
if (!mssqlKeywords.Contains("TOP") || !mssqlKeywords.Contains("IDENTITY"))
{
throw new Exception("MSSQL keywords missing expected dialect keywords!");
}
if (!pgKeywords.Contains("RETURNING") || !pgKeywords.Contains("SERIAL"))
{
throw new Exception("PostgreSQL keywords missing expected dialect keywords!");
}
if (!mysqlKeywords.Contains("AUTO_INCREMENT"))
{
throw new Exception("MySQL keywords missing AUTO_INCREMENT!");
}
if (!sqliteKeywords.Contains("AUTOINCREMENT"))
{
throw new Exception("SQLite keywords missing AUTOINCREMENT!");
}
var mssqlSnippets = await storageService.GetSnippetsAsync("MSSQL");
var pgSnippets = await storageService.GetSnippetsAsync("PostgreSQL");
Console.WriteLine($"Snippets count: MSSQL={mssqlSnippets.Count}, PostgreSQL={pgSnippets.Count}");
if (mssqlSnippets.Count == 0 || pgSnippets.Count == 0)
{
throw new Exception("Dialect snippets failed to load from SQLite!");
}
// Test Dynamic Dialect Switching in SqlKeywordsService
Console.WriteLine("\n[Action] Testing Dynamic Dialect Switching in SqlKeywordsService...");
await Fdb.Services.SqlKeywordsService.Instance.LoadKeywordsFromDatabaseAsync("PostgreSQL");
if (Fdb.Services.SqlKeywordsService.Instance.CurrentDialect != "PostgreSQL" || !Fdb.Services.SqlKeywordsService.Instance.Keywords.Contains("RETURNING"))
{
throw new Exception("SqlKeywordsService failed to switch to PostgreSQL dialect!");
}
Console.WriteLine($"Switched to PostgreSQL. Contains 'RETURNING': {Fdb.Services.SqlKeywordsService.Instance.Keywords.Contains("RETURNING")}");
await Fdb.Services.SqlKeywordsService.Instance.LoadKeywordsFromDatabaseAsync("MSSQL");
if (Fdb.Services.SqlKeywordsService.Instance.CurrentDialect != "MSSQL" || !Fdb.Services.SqlKeywordsService.Instance.Keywords.Contains("TOP"))
{
throw new Exception("SqlKeywordsService failed to switch back to MSSQL dialect!");
}
Console.WriteLine($"Switched back to MSSQL. Contains 'TOP': {Fdb.Services.SqlKeywordsService.Instance.Keywords.Contains("TOP")}");
var connections = await storageService.GetRecentConnectionsAsync();
Console.WriteLine($"SQLite Connections loaded: {connections.Count} connection(s).");
var history = await storageService.GetQueryHistoryAsync();
Console.WriteLine($"SQLite QueryHistory loaded: {history.Count} record(s).");
var cachedRows = await storageService.GetQueryResultsAsync();
Console.WriteLine($"SQLite QueryResults loaded: {cachedRows.Count} row(s).");
// Test saving new query to SQLite
await storageService.SaveQueryHistoryAsync("SELECT id, name FROM Users WHERE status = 'Active';", "6 ms", 1, "Success");
var updatedHistory = await storageService.GetQueryHistoryAsync();
Console.WriteLine($"SQLite QueryHistory updated count: {updatedHistory.Count} record(s).");
if (updatedHistory.Count == 0)
{
throw new Exception("Failed to persist query history in SQLite!");
}
// 11. Test Dynamic Database Providers & Connect Modal Engine Switching
Console.WriteLine("\n[Action] Verifying Dynamic Database Providers & Engine Switching...");
var registry = Fdb.Services.DatabaseProviders.DatabaseProviderRegistry.Instance;
var allProviders = registry.GetAllProviders();
Console.WriteLine($"Registered Providers: {string.Join(", ", System.Linq.Enumerable.Select(allProviders, p => p.Id))}");
if (allProviders.Count < 4)
{
throw new Exception("Expected at least 4 registered database providers (MSSQL, PostgreSQL, MySQL, SQLite)!");
}
var mssqlProv = registry.GetProvider("MSSQL");
var connStr = mssqlProv.BuildConnectionString(new Fdb.Services.DatabaseProviders.DatabaseConnectionConfig
{
Server = "127.0.0.1",
Port = 1433,
Database = "TestDb",
AuthType = "SQL Server Authentication",
Username = "sa",
Password = "SecretPassword123!",
TrustServerCertificate = true
});
Console.WriteLine($"Generated MSSQL Connection String: {connStr}");
if (!connStr.Contains("TestDb") || !connStr.Contains("sa") || !connStr.Contains("127.0.0.1"))
{
throw new Exception("MSSQL Connection string did not contain expected configuration parameters!");
}
// Test SQLite Provider In-Memory
var sqliteProv = registry.GetProvider("SQLite");
var sqliteTest = await sqliteProv.TestConnectionAsync(new Fdb.Services.DatabaseProviders.DatabaseConnectionConfig
{
FilePath = ":memory:"
});
Console.WriteLine($"SQLite In-Memory Test: Success={sqliteTest.Success}, Version='{sqliteTest.ServerVersion}'");
if (!sqliteTest.Success || string.IsNullOrEmpty(sqliteTest.ServerVersion))
{
throw new Exception("SQLite In-Memory test failed!");
}
// Test ConnectModal dynamic switching
vm.ConnectModal.SelectEngine("PostgreSQL");
Console.WriteLine($"Switched to PostgreSQL: Port={vm.ConnectModal.Port}, Database={vm.ConnectModal.Database}, Username={vm.ConnectModal.Username}, IsServerBased={vm.ConnectModal.IsServerBased}");
if (vm.ConnectModal.Port != 5432 || vm.ConnectModal.Database != "postgres" || vm.ConnectModal.Username != "postgres")
{
throw new Exception("ConnectModal failed to switch default parameters for PostgreSQL!");
}
vm.ConnectModal.SelectEngine("SQLite");
Console.WriteLine($"Switched to SQLite: IsFileBased={vm.ConnectModal.IsFileBased}, IsServerBased={vm.ConnectModal.IsServerBased}");
if (!vm.ConnectModal.IsFileBased || vm.ConnectModal.IsServerBased)
{
throw new Exception("ConnectModal failed to switch to file-based mode for SQLite!");
}
vm.ConnectModal.SelectEngine("MSSQL");
Console.WriteLine($"Switched back to MSSQL: Port={vm.ConnectModal.Port}, Database={vm.ConnectModal.Database}, Username={vm.ConnectModal.Username}");
if (vm.ConnectModal.Port != 1433 || vm.ConnectModal.Database != "master")
{
throw new Exception("ConnectModal failed to restore default parameters for MSSQL!");
}
// 12. Test Startup Disconnection & Target Isolation (No auto-connection to internal SQLite)
Console.WriteLine("\n[Action] Verifying Startup Disconnection & Target Isolation...");
Console.WriteLine($"Startup State: Sidebar.IsConnected={vm.Sidebar.IsConnected}, SelectedTarget={vm.SelectedTargetDatabase?.DisplayName ?? "null"}, TargetDbName={vm.SampleQueryResult.TargetDatabaseName}");
if (vm.Sidebar.IsConnected || vm.SelectedTargetDatabase != null)
{
throw new Exception("Application must NOT auto-connect to internal sqlite on startup!");
}
if (vm.SampleQueryResult.TargetDatabaseName != "(No Connection)")
{
throw new Exception($"Expected target '(No Connection)', got '{vm.SampleQueryResult.TargetDatabaseName}'!");
}
// Test executing query while disconnected returns friendly warning without querying internal sqlite
vm.Editor.SelectedTab!.Text = "SELECT 1;";
await vm.ExecuteQuery();
Console.WriteLine($"Disconnected execution: Status={vm.SampleQueryResult.Rows[0]["Status"]}, Message={vm.SampleQueryResult.Rows[0]["Message"]}");
if (vm.SampleQueryResult.Rows[0]["Status"] != "Not Connected")
{
throw new Exception("Query execution without connection must return Not Connected status!");
}
// 13. Test Hierarchical Tree View: Adding connection, clicking to connect, displaying tables in tree
Console.WriteLine("\n[Action] Verifying Hierarchical Tree View & Connecting from Tree...");
int testDbId = await Fdb.Services.DatabaseStorageService.Instance.SaveConnectionAsync(
":memory:", 0, "TestStudioDB", "None", "sa", "SQLite");
await vm.Sidebar.RefreshAsync();
await vm.RefreshAvailableDatabasesAsync();
var testDbNode = System.Linq.Enumerable.FirstOrDefault(vm.Sidebar.DatabaseNodes, n => n.DatabaseName == "TestStudioDB");
if (testDbNode == null)
{
throw new Exception("Failed to locate TestStudioDB in Sidebar DatabaseNodes!");
}
Console.WriteLine($"Located Tree Node: Title={testDbNode.Title}, IsConnected={testDbNode.IsConnected}");
if (testDbNode.IsConnected)
{
throw new Exception("New database in tree should initially be Disconnected until clicked!");
}
// Click (+) Expand or Right-Click Connect to TestStudioDB from tree
Console.WriteLine($"Clicking/connecting to tree node '{testDbNode.Title}' via ToggleExpandCommand (+)...");
await testDbNode.ToggleExpandCommand.ExecuteAsync(null);
Console.WriteLine($"After click: Node.IsConnected={testDbNode.IsConnected}, Node.IsActive={testDbNode.IsActive}, Node.IsExpanded={testDbNode.IsExpanded}");
if (!testDbNode.IsConnected || !testDbNode.IsActive || !testDbNode.IsExpanded)
{
throw new Exception("Clicking database node failed to connect and expand in tree!");
}
// Verify children hierarchy (Folder node 'Tables' -> Table items)
if (testDbNode.Children.Count == 0 || testDbNode.TablesFolder == null)
{
throw new Exception("Database node in tree must contain Tables folder node after connection!");
}
Console.WriteLine($"Tree Folder: '{testDbNode.TablesFolder.Title}', Child items count={testDbNode.TablesFolder.Children.Count}");
// Create a table with rows in the connected database and verify it appears in the tree
await Fdb.Services.DatabaseProviders.DatabaseSessionService.Instance.ExecuteQueryAsync("CREATE TABLE Users (Id INTEGER PRIMARY KEY, Name TEXT);", testDbNode.ConnectionKey);
await Fdb.Services.DatabaseProviders.DatabaseSessionService.Instance.ExecuteQueryAsync("INSERT INTO Users (Id, Name) VALUES (1, 'Alice'), (2, 'Bob');", testDbNode.ConnectionKey);
await vm.Sidebar.RefreshDatabaseNodeAsync(testDbNode);
Console.WriteLine($"After table creation: Tree Folder '{testDbNode.TablesFolder.Title}', Child count={testDbNode.TablesFolder.Children.Count}");
if (testDbNode.TablesFolder.Children.Count == 0)
{
throw new Exception("Expected at least 1 table in tree folder after CREATE TABLE!");
}
var tableItem = testDbNode.TablesFolder.Children[0] as Fdb.ViewModels.TableTreeNode;
Console.WriteLine($"Found Table in Tree: {tableItem?.FullName}");
if (tableItem?.TableName != "Users")
{
throw new Exception($"Expected TableName 'Users', got '{tableItem?.TableName}'!");
}
// Test clicking table node: Automatically generates TOP 100 query, executes it and populates TOP 100 items into table
await vm.Sidebar.SelectTableNode(tableItem);
Console.WriteLine($"Editor text after clicking table: {vm.Editor.SelectedTab!.Text}");
Console.WriteLine($"Auto-executed Table Rows Count: {vm.SampleQueryResult.TotalRows}, Footer.IsVisible: {vm.Footer.IsVisible}");
if (!vm.Editor.SelectedTab.Text.Contains("Users") || !vm.Editor.SelectedTab.Text.Contains("100"))
{
throw new Exception("Clicking table in tree failed to generate TOP/LIMIT 100 query in editor!");
}
if (vm.SampleQueryResult.TotalRows != 2)
{
throw new Exception($"Expected 2 rows from auto-executed table query, got {vm.SampleQueryResult.TotalRows}!");
}
if (vm.SampleQueryResult.Rows[0]["Name"] != "Alice" || vm.SampleQueryResult.Rows[1]["Name"] != "Bob")
{
throw new Exception("Auto-executed table query returned unexpected row data!");
}
// Verify Active/Highlighted state on clicked TableTreeNode
if (!tableItem.IsActive || vm.Sidebar.ActiveTableNode != tableItem)
{
throw new Exception("Clicked table node is not marked as Active!");
}
Console.WriteLine($"[Success] TableTreeNode is highlighted and active: IsActive={tableItem.IsActive}, ActiveTableNode={vm.Sidebar.ActiveTableNode?.FullName}");
// Verify Real-time Search & Filtering of Tables in Active Database
Console.WriteLine("\n[Action] Verifying Sidebar Active Database Table Searching & Filtering...");
Console.WriteLine($"Active Database: {testDbNode.DatabaseName}, Initial Tables Count: {testDbNode.TablesFolder.Children.Count}");
// 1. Search matching table name 'user'
vm.Sidebar.TableSearchFilter = "user";
Console.WriteLine($"Filtered with 'user': Tables Count = {testDbNode.TablesFolder.Children.Count}, HasSearchFilter = {vm.Sidebar.HasSearchFilter}");
if (testDbNode.TablesFolder.Children.Count != 1)
{
throw new Exception($"Expected 1 matching table for 'user', got {testDbNode.TablesFolder.Children.Count}!");
}
if (((Fdb.ViewModels.TableTreeNode)testDbNode.TablesFolder.Children[0]).TableName != "Users")
{
throw new Exception("Filtered table did not match 'Users'!");
}
// 2. Search non-existent table name 'nonexistent'
vm.Sidebar.TableSearchFilter = "nonexistent";
Console.WriteLine($"Filtered with 'nonexistent': Tables Count = {testDbNode.TablesFolder.Children.Count}");
if (testDbNode.TablesFolder.Children.Count != 0)
{
throw new Exception($"Expected 0 matching tables for 'nonexistent', got {testDbNode.TablesFolder.Children.Count}!");
}
// 3. Test ClearSearchFilterCommand
vm.Sidebar.ClearSearchFilterCommand.Execute(null);
Console.WriteLine($"After clear: Filter='{vm.Sidebar.TableSearchFilter}', Tables Count = {testDbNode.TablesFolder.Children.Count}");
if (testDbNode.TablesFolder.Children.Count != 1 || vm.Sidebar.HasSearchFilter)
{
throw new Exception("Clearing search filter failed to restore all tables!");
}
// 4. Verify tableItem still retains its Active state
if (!tableItem.IsActive || vm.Sidebar.ActiveTableNode != tableItem)
{
throw new Exception("Table node did not stay active/highlighted after filter clear!");
}
Console.WriteLine("[Success] Real-time table searching, filtering, and active table highlighting verified!");
// 13.5 Test Table Auto-Completion from Active Database (e.g. SELECT * FROM u -> Users)
Console.WriteLine("\n[Action] Verifying Active Database Table Auto-Completion in Editor...");
Console.WriteLine($"Active Database in KeywordsService: '{Fdb.Services.SqlKeywordsService.Instance.ActiveDatabaseName}', Tables Count: {Fdb.Services.SqlKeywordsService.Instance.ActiveTables.Count}");
if (Fdb.Services.SqlKeywordsService.Instance.ActiveTables.Count != 1 || Fdb.Services.SqlKeywordsService.Instance.ActiveTables[0].TableName != "Users")
{
throw new Exception($"Expected active tables to contain 'Users', got {Fdb.Services.SqlKeywordsService.Instance.ActiveTables.Count} table(s)!");
}
bool isFromTableContext = Fdb.Components.SqlIntelliSenseOverlayComponent.IsTableContext("SELECT * FROM ");
bool isJoinTableContext = Fdb.Components.SqlIntelliSenseOverlayComponent.IsTableContext("SELECT * FROM Orders JOIN ");
bool isSelectContext = Fdb.Components.SqlIntelliSenseOverlayComponent.IsTableContext("SELECT ");
Console.WriteLine($"Context Detection: 'FROM ' -> {isFromTableContext}, 'JOIN ' -> {isJoinTableContext}, 'SELECT ' -> {isSelectContext}");
if (!isFromTableContext || !isJoinTableContext || isSelectContext)
{
throw new Exception("IsTableContext returned incorrect result for SQL clauses!");
}
Console.WriteLine("[Success] Active database table auto-completion verified!");
// Verify SelectedTargetDatabase is now set to this database
if (vm.SelectedTargetDatabase == null || vm.SelectedTargetDatabase.DatabaseName != "TestStudioDB")
{
throw new Exception($"Expected active target 'TestStudioDB', got '{vm.SelectedTargetDatabase?.DisplayName}'!");
}
// 14. Test Real Query Execution into Table on Connected Database
Console.WriteLine("\n[Action] Verifying Real Query Execution on Connected Database...");
vm.Editor.SelectedTab!.Text = "SELECT 42 AS Answer, 'Antigravity' AS Assistant, 'Active' AS Status;";
await vm.ExecuteQuery();
Console.WriteLine($"Query executed! Total Rows: {vm.SampleQueryResult.TotalRows}, ExecutionTime: {vm.SampleQueryResult.ExecutionTime}");
if (vm.SampleQueryResult.TotalRows == 0)
{
throw new Exception("Real query execution failed to return rows!");
}
var resultRow = vm.SampleQueryResult.Rows[0];
Console.WriteLine($"Real Result Row: Answer={resultRow["Answer"]}, Assistant={resultRow["Assistant"]}, Status={resultRow["Status"]}");
if (resultRow["Answer"] != "42" || resultRow["Assistant"] != "Antigravity")
{
throw new Exception($"Expected dynamic column values '42' and 'Antigravity', got '{resultRow["Answer"]}' and '{resultRow["Assistant"]}'!");
}
// 15. Test Saving Connections in Connect Modal and SQLite Persistence
Console.WriteLine("\n[Action] Verifying Connection Persistence in SQLite...");
int initialSaved = vm.Sidebar.SavedConnections.Count;
vm.ConnectModal.Open();
vm.ConnectModal.Server = "sql-cluster.production.local";
vm.ConnectModal.Port = 1433;
vm.ConnectModal.Database = "SalesDB";
vm.ConnectModal.Username = "sales_admin";
// Persist connection
await Fdb.Services.DatabaseStorageService.Instance.SaveConnectionAsync(
vm.ConnectModal.Server, vm.ConnectModal.Port, vm.ConnectModal.Database,
vm.ConnectModal.AuthType, vm.ConnectModal.Username, "MSSQL");
await vm.Sidebar.RefreshAsync();
await vm.ConnectModal.LoadSavedConnectionsAsync();
Console.WriteLine($"Saved Connections Count: {vm.ConnectModal.SavedConnections.Count} (was {initialSaved})");
var savedConn = System.Linq.Enumerable.FirstOrDefault(vm.ConnectModal.SavedConnections, c => c.Server == "sql-cluster.production.local");