mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-05-27 10:32:44 -05:00
503 lines
19 KiB
C#
503 lines
19 KiB
C#
using HavenSoft.HexManiac.Core;
|
|
using HavenSoft.HexManiac.Core.Models;
|
|
using HavenSoft.HexManiac.Core.ViewModels;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Windows.Input;
|
|
using Xunit;
|
|
|
|
namespace HavenSoft.HexManiac.Tests {
|
|
public class FindTests {
|
|
private static Func<int, int, ChildViewPort> CreateCreateChildView(Func<IViewPort> tab) => (start, end) => new ChildViewPort(tab(), InstantDispatch.Instance, BaseViewModelTestClass.Singletons);
|
|
private static ViewPort NewViewPort(IDataModel model) => new ViewPort("test.gba", model, InstantDispatch.Instance, BaseViewModelTestClass.Singletons);
|
|
private static Func<string, bool, (int, int)[]> DefaultFind(params (int, int)[] results) => (text, matchExactCase) => results;
|
|
|
|
[Fact]
|
|
public void FindCanFindSingle() {
|
|
var array = new byte[0x1000];
|
|
new byte[] { 0x84, 0x23, 0xBB, 0x21 }.CopyTo(array, 0x240);
|
|
var port = new ViewPort(new LoadedFile("test", array));
|
|
|
|
var results = port.Find("84 23 BB 21");
|
|
|
|
Assert.Single(results);
|
|
Assert.Equal(0x240, results[0].start);
|
|
}
|
|
|
|
[Fact]
|
|
public void FindCanFindMultiple() {
|
|
var array = new byte[0x1000];
|
|
var searchFor = new byte[] { 0x84, 0x23, 0xBB, 0x21 };
|
|
searchFor.CopyTo(array, 0x240);
|
|
searchFor.CopyTo(array, 0xA70);
|
|
var port = new ViewPort(new LoadedFile("test", array));
|
|
|
|
var results = port.Find("84 23 BB 21").Select(result => result.start).ToList();
|
|
|
|
Assert.Equal(2, results.Count);
|
|
Assert.Contains(0x240, results);
|
|
Assert.Contains(0xA70, results);
|
|
}
|
|
|
|
[Fact]
|
|
public void EditorShowsErrorIfNoneFound() {
|
|
var tab = new StubViewPort();
|
|
var editor = new EditorViewModel(new StubFileSystem()) { tab };
|
|
|
|
tab.Find = DefaultFind();
|
|
editor.Find.Execute("something");
|
|
|
|
Assert.True(editor.ShowError);
|
|
Assert.False(string.IsNullOrEmpty(editor.ErrorMessage));
|
|
}
|
|
|
|
[Fact]
|
|
public void EditorJumpsToResultIfSingleResult() {
|
|
var tab = new StubViewPort();
|
|
var editor = new EditorViewModel(new StubFileSystem()) { tab };
|
|
string gotoArg = string.Empty;
|
|
|
|
tab.Find = DefaultFind((0x54, 0x54));
|
|
tab.Goto = new StubCommand { CanExecute = arg => true, Execute = arg => gotoArg = (string)arg };
|
|
editor.Find.Execute("something");
|
|
|
|
Assert.False(editor.ShowError);
|
|
Assert.Equal("54", gotoArg);
|
|
}
|
|
|
|
[Fact]
|
|
public void EditorOpensHelperTabIfMultipleResult() {
|
|
var tab = new StubViewPort();
|
|
var editor = new EditorViewModel(new StubFileSystem()) { tab };
|
|
var count = 0;
|
|
|
|
tab.Find = DefaultFind((0x54, 0x54), (0x154, 0x154));
|
|
tab.Model = new BasicModel(new byte[0x200]);
|
|
tab.CreateChildView = (int startAddress, int endAddress) => {
|
|
var child = new ChildViewPort(tab, InstantDispatch.Instance, BaseViewModelTestClass.Singletons);
|
|
count++;
|
|
return child;
|
|
};
|
|
editor.Find.Execute("something");
|
|
|
|
Assert.Equal(2, count); // since there were 2 results, editor should've asked for 2 child views
|
|
Assert.Equal(2, editor.Count);
|
|
Assert.Equal(1, editor.SelectedIndex);
|
|
Assert.IsType<SearchResultsViewPort>(editor[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public void EditorHasShortcutsToGetPreviousOrNextFindResult() {
|
|
int gotoCount = 0;
|
|
StubViewPort tab = null;
|
|
tab = new StubViewPort {
|
|
Find = DefaultFind((0x54, 0x54), (0x154, 0x154)),
|
|
Model = new BasicModel(new byte[0x200]),
|
|
Goto = new StubCommand { CanExecute = arg => true, Execute = arg => gotoCount++ },
|
|
CreateChildView = (start, end) => new ChildViewPort(tab, InstantDispatch.Instance, BaseViewModelTestClass.Singletons),
|
|
Headers = new ObservableCollection<string> { "00", "01", "02", "03" },
|
|
Width = 4,
|
|
Height = 4,
|
|
};
|
|
var editor = new EditorViewModel(new StubFileSystem()) { tab };
|
|
|
|
editor.Find.Execute("something");
|
|
editor.SelectedIndex = 0;
|
|
editor.FindNext.Execute("something");
|
|
editor.FindPrevious.Execute("something");
|
|
|
|
Assert.Equal(2, gotoCount); // findNext / findPrevious use goto
|
|
}
|
|
|
|
[Fact]
|
|
public void EditorFindNextDoesNotSwitchTabs() {
|
|
StubViewPort tab1 = null;
|
|
tab1 = new StubViewPort {
|
|
Find = DefaultFind((0x60, 0x60)),
|
|
Goto = new StubCommand(),
|
|
Model = new BasicModel(new byte[0x200]),
|
|
CreateChildView = CreateCreateChildView(() => tab1),
|
|
Headers = new ObservableCollection<string> { "00", "01", "02", "03" },
|
|
Width = 4,
|
|
Height = 4,
|
|
};
|
|
StubViewPort tab2 = null;
|
|
tab2 = new StubViewPort {
|
|
Find = DefaultFind((0x50, 0x50), (0x70, 0x70)),
|
|
Goto = new StubCommand(),
|
|
Model = new BasicModel(new byte[0x200]),
|
|
CreateChildView = CreateCreateChildView(() => tab2),
|
|
Headers = new ObservableCollection<string> { "00", "01", "02", "03" },
|
|
Width = 4,
|
|
Height = 4,
|
|
};
|
|
var editor = new EditorViewModel(new StubFileSystem()) { tab1, tab2 };
|
|
|
|
editor.Find.Execute("something");
|
|
|
|
editor.FindNext.Execute("something");
|
|
Assert.Equal(2, editor.SelectedIndex); // results still selected
|
|
|
|
editor.SelectedIndex = 0;
|
|
editor.FindNext.Execute("something");
|
|
Assert.Equal(0, editor.SelectedIndex); // results in first tab selected
|
|
|
|
editor.SelectedIndex = 1;
|
|
editor.FindNext.Execute("something");
|
|
Assert.Equal(1, editor.SelectedIndex); // results in second tab selected
|
|
}
|
|
|
|
[Fact]
|
|
public void FindResultsHasHeadersAndGaps() {
|
|
StubViewPort tab = null;
|
|
tab = new StubViewPort {
|
|
Find = DefaultFind((0x50, 0x50), (0x70, 0x70)),
|
|
Goto = new StubCommand(),
|
|
Model = new BasicModel(new byte[0x200]),
|
|
CreateChildView = CreateCreateChildView(() => tab),
|
|
Headers = new ObservableCollection<string> { "00", "01", "02", "03" },
|
|
Width = 4,
|
|
Height = 4,
|
|
};
|
|
var editor = new EditorViewModel(new StubFileSystem()) { tab };
|
|
|
|
editor.Find.Execute("something");
|
|
var results = (IViewPort)editor[1];
|
|
results.Height = 9; // both children are size 4, one space inbetween
|
|
Assert.False(results.Headers.All(string.IsNullOrEmpty)); // not all the headers are blank
|
|
Assert.Contains(results.Headers, string.IsNullOrEmpty); // blank lines have blank headers
|
|
}
|
|
|
|
[Fact]
|
|
public void FindClosesAfterRun() {
|
|
StubViewPort tab = null;
|
|
tab = new StubViewPort {
|
|
Find = DefaultFind((0x50, 0x50), (0x70, 0x70)),
|
|
Goto = new StubCommand(),
|
|
Model = new BasicModel(new byte[0x200]),
|
|
CreateChildView = CreateCreateChildView(() => tab),
|
|
Headers = new ObservableCollection<string> { "00", "01", "02", "03" },
|
|
Width = 4,
|
|
Height = 4,
|
|
};
|
|
var editor = new EditorViewModel(new StubFileSystem()) { tab };
|
|
|
|
editor.ShowFind.Execute(true);
|
|
editor.Find.Execute("something");
|
|
|
|
Assert.False(editor.FindControlVisible);
|
|
}
|
|
|
|
[Fact]
|
|
public void CompositeCanScroll() {
|
|
var composite = new SearchResultsViewPort("search") { Height = 0x10 };
|
|
var parentData = new byte[0x100];
|
|
var parent = new StubViewPort { Width = 0x10, Height = 0x10, Model = new BasicModel(parentData) };
|
|
composite.Add(CreateCreateChildView(() => parent)(0, 0), 0, 0);
|
|
|
|
var bodyChanged = false;
|
|
var headerChanged = false;
|
|
composite.Headers.CollectionChanged += (sender, e) => headerChanged = true;
|
|
composite.CollectionChanged += (sender, e) => bodyChanged = true;
|
|
|
|
composite.ScrollValue = 4;
|
|
|
|
Assert.True(bodyChanged);
|
|
Assert.True(headerChanged);
|
|
}
|
|
|
|
[Fact]
|
|
public void FindOrderShouldBeBasedOnCurrentSelection() {
|
|
var data = new byte[0x100];
|
|
var dataToFind = new byte[] { 0x52, 0xDC, 0xFF, 0x79 };
|
|
dataToFind.CopyTo(data, 0x2);
|
|
dataToFind.CopyTo(data, 0x62);
|
|
dataToFind.CopyTo(data, 0xA8);
|
|
dataToFind.CopyTo(data, 0xCC);
|
|
var viewPort = new ViewPort(new LoadedFile("test", data)) { Width = 8, Height = 8 };
|
|
viewPort.SelectionStart = new Point(0, 2); // select byte 0x10
|
|
|
|
var results = viewPort.Find("52 DC FF 79").Select(result => result.start).ToList(); ;
|
|
|
|
// the earliest match is at the end because the search started where the cursor was and looped around.
|
|
Assert.True(results.SequenceEqual(new[] { 0x62, 0xA8, 0xCC, 0x02 }));
|
|
}
|
|
|
|
/// <summary>
|
|
/// editor starts with most recently added tab selected.
|
|
/// if a single result is found, the editor should switch to the tab that contains it.
|
|
/// </summary>
|
|
[Fact]
|
|
public void FindSwitchesTabIfSingleResultIsOnAnotherTab() {
|
|
var tab0 = new StubViewPort { Goto = new StubCommand() };
|
|
var tab1 = new StubTabContent();
|
|
var editor = new EditorViewModel(new StubFileSystem()) { tab0, tab1 };
|
|
|
|
tab0.Find = DefaultFind((0x50, 0x50));
|
|
editor.Find.Execute("search");
|
|
|
|
Assert.Equal(0, editor.SelectedIndex);
|
|
}
|
|
|
|
[Fact]
|
|
public void FindNextFindPreviousDisabledIfNoResults() {
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
|
|
Assert.False(editor.FindPrevious.CanExecute("text"));
|
|
Assert.False(editor.FindNext.CanExecute("text"));
|
|
}
|
|
|
|
[Fact]
|
|
public void FindNextAndPreviousBecomePossibleAfterFindSingleResult() {
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
StubViewPort tab = null;
|
|
tab = new StubViewPort {
|
|
Find = DefaultFind((0x50, 0x50)),
|
|
Goto = new StubCommand(),
|
|
CreateChildView = CreateCreateChildView(() => tab),
|
|
Headers = new ObservableCollection<string> { "00", "01", "02", "03" },
|
|
Width = 4,
|
|
Height = 4,
|
|
};
|
|
editor.Add(tab);
|
|
|
|
editor.Find.Execute("text");
|
|
|
|
Assert.True(editor.FindPrevious.CanExecute("text"));
|
|
Assert.True(editor.FindNext.CanExecute("text"));
|
|
}
|
|
|
|
[Fact]
|
|
public void FindNextAndPreviousBecomePossibleAfterTabSwitchAfterFindMultiResult() {
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
StubViewPort tab = null;
|
|
tab = new StubViewPort {
|
|
Find = DefaultFind((0x50, 0x50), (0x70, 0x70)),
|
|
Goto = new StubCommand(),
|
|
Model = new BasicModel(new byte[0x200]),
|
|
CreateChildView = CreateCreateChildView(() => tab),
|
|
Headers = new ObservableCollection<string> { "00", "01", "02", "03" },
|
|
Width = 4,
|
|
Height = 4,
|
|
};
|
|
editor.Add(tab);
|
|
|
|
editor.Find.Execute("text");
|
|
Assert.False(editor.FindPrevious.CanExecute("text"));
|
|
Assert.False(editor.FindNext.CanExecute("text"));
|
|
|
|
editor.SelectedIndex = 0;
|
|
Assert.True(editor.FindPrevious.CanExecute("text"));
|
|
Assert.True(editor.FindNext.CanExecute("text"));
|
|
}
|
|
|
|
[Fact]
|
|
public void FindRaisesMessageForGoodResults() {
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
var data = new byte[0x100];
|
|
var dataToFind = new byte[] { 0x52, 0xDC, 0xFF, 0x79 };
|
|
dataToFind.CopyTo(data, 0x2);
|
|
var viewPort = new ViewPort(new LoadedFile("test", data)) { Width = 8, Height = 8 };
|
|
editor.Add(viewPort);
|
|
editor.Find.Execute("52 DC FF 79");
|
|
|
|
Assert.True(editor.ShowMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public void SearchResultsHaveFullResultsSelected() {
|
|
var text = "This is the song that never ends.";
|
|
var bytes = PCSString.Convert(text).ToArray();
|
|
var buffer = new byte[0x200];
|
|
Array.Copy(bytes, 0, buffer, 0x30, bytes.Length); // two copies of the data
|
|
Array.Copy(bytes, 0, buffer, 0x60, bytes.Length); // at reasonable locations
|
|
var model = new PokemonModel(buffer);
|
|
var viewPort = new ViewPort("test.gba", model, InstantDispatch.Instance, BaseViewModelTestClass.Singletons) { Width = 0x10, Height = 0x10 };
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
editor.Add(viewPort);
|
|
|
|
editor.Find.Execute("This is the song");
|
|
var resultsTab = (IViewPort)editor[editor.SelectedIndex];
|
|
resultsTab.Width = 0x10;
|
|
resultsTab.Height = 0x10;
|
|
|
|
int selectedCellCount = 0;
|
|
for (int x = 0; x < resultsTab.Width; x++) {
|
|
selectedCellCount +=
|
|
resultsTab.Height.Range()
|
|
.Select(y => new Point(x, y))
|
|
.Count(resultsTab.IsSelected);
|
|
}
|
|
|
|
Assert.InRange(selectedCellCount, 30, 40);
|
|
}
|
|
|
|
[Fact]
|
|
public void FollowingASearchResultSelectsEntireResult() {
|
|
var text = "This is the song that never ends.";
|
|
var bytes = PCSString.Convert(text).ToArray();
|
|
var buffer = new byte[0x200];
|
|
Array.Copy(bytes, 0, buffer, 0x30, bytes.Length); // two copies of the data
|
|
Array.Copy(bytes, 0, buffer, 0x60, bytes.Length); // at reasonable locations
|
|
var model = new PokemonModel(buffer);
|
|
var viewPort = NewViewPort(model);
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
editor.Add(viewPort);
|
|
|
|
editor.Find.Execute("This is the song");
|
|
var resultsTab = (IViewPort)editor[editor.SelectedIndex];
|
|
resultsTab.FollowLink(0, 1);
|
|
|
|
Assert.NotEqual(viewPort.SelectionStart, viewPort.SelectionEnd);
|
|
}
|
|
|
|
[Fact]
|
|
public void SearchCanFindTableRows() {
|
|
// Arrange two tables, one that depends on the other
|
|
var data = 0x200.Range().Select(i => (byte)0xFF).ToArray();
|
|
var model = new PokemonModel(data);
|
|
var viewPort = NewViewPort(model);
|
|
viewPort.Edit("^names[entry\"\"8] +\"bob\" +\"sam\" +\"carl\" +\"steve\" ");
|
|
viewPort.Edit("@50 ^table[x: y:]names ");
|
|
|
|
// Act: do a search that should return a table entry as a result
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
editor.Add(viewPort);
|
|
editor.Find.Execute("carl");
|
|
|
|
// Assert: there are 2 results, one for the text and one for the table
|
|
Assert.Contains("2", editor.InformationMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public void SearchCanFindEnumUsages() {
|
|
// Arrange two tables, one that depends on the other
|
|
var data = 0x200.Range().Select(i => (byte)0xFF).ToArray();
|
|
var model = new PokemonModel(data);
|
|
var viewPort = NewViewPort(model);
|
|
viewPort.Edit("^names[entry\"\"8] +\"bob\" +\"sam\" +\"carl\" +\"steve\" ");
|
|
viewPort.Edit("@50 ^table[x: y:names]2 12 sam 100 steve ");
|
|
|
|
// Act: do a search that should return a table entry as a result
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
editor.Add(viewPort);
|
|
editor.Find.Execute("steve");
|
|
|
|
// Assert: there are 2 results, one for the text and one for the enum
|
|
Assert.Contains("2", editor.InformationMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public void FindingSingleResultHighlightsEntireResult() {
|
|
var data = 0x100.Range().Select(i => (byte)i).ToArray();
|
|
var model = new PokemonModel(data);
|
|
var viewPort = NewViewPort(model);
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
editor.Add(viewPort);
|
|
|
|
editor.Find.Execute("05 06 07 08");
|
|
|
|
Assert.NotEqual(viewPort.SelectionStart, viewPort.SelectionEnd);
|
|
}
|
|
|
|
[Fact]
|
|
public void SameModelDoesNotGetSearchedMultipleTimes() {
|
|
int findCalls = 0;
|
|
IReadOnlyList<(int, int)> Find(string input, bool matchExactCase = false) {
|
|
findCalls += 1;
|
|
return new List<(int, int)>();
|
|
}
|
|
var viewPort1 = new StubViewPort { Model = SelfEqualStub(), Find = Find };
|
|
var viewPort2 = new StubViewPort { Model = viewPort1.Model, Find = Find };
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
editor.Add(viewPort1);
|
|
editor.Add(viewPort2);
|
|
|
|
editor.Find.Execute("sample");
|
|
|
|
Assert.Equal(1, findCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public void DifferentModelsAllGetSearched() {
|
|
int findCalls = 0;
|
|
IReadOnlyList<(int, int)> Find(string input, bool matchExactCase = false) {
|
|
findCalls += 1;
|
|
return new List<(int, int)>();
|
|
}
|
|
var viewPort1 = new StubViewPort { Model = SelfEqualStub(), Find = Find };
|
|
var viewPort2 = new StubViewPort { Model = SelfEqualStub(), Find = Find };
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
editor.Add(viewPort1);
|
|
editor.Add(viewPort2);
|
|
|
|
editor.Find.Execute("sample");
|
|
|
|
Assert.Equal(2, findCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public void FindText_CloseFind_StopVisuallyFiltering() {
|
|
var editor = new EditorViewModel(new StubFileSystem());
|
|
|
|
editor.ShowFind.Execute(true);
|
|
editor.FindText = "40";
|
|
Assert.Equal(0x40, editor.SearchByte);
|
|
|
|
editor.ShowFind.Execute(false);
|
|
Assert.Equal(-1, editor.SearchByte);
|
|
}
|
|
|
|
[Fact]
|
|
public void SearchText_ContainsWildcard_SearchForMatches() {
|
|
var test = new BaseViewModelTestClass();
|
|
Array.Copy(new byte[] { 0x20, 0x30, 0x40, 0x50 }, test.Model.RawData, 4);
|
|
|
|
var results = test.ViewPort.Find("20 30 XX 50");
|
|
|
|
Assert.Single(results);
|
|
Assert.Equal(0, results[0].start);
|
|
Assert.Equal(3, results[0].end);
|
|
}
|
|
|
|
[Fact]
|
|
public void MultipleResults_CloseTogether_CombineTogether() {
|
|
var test = new BaseViewModelTestClass();
|
|
test.CreateTextTable("options", 0x100, "abc", "def", "ijk", "lmn", "qrs", "tuv", "xyz");
|
|
test.ViewPort.Edit("^table[a::options b:: c:: d::]4 ");
|
|
var editor = new EditorViewModel(new StubFileSystem(), InstantDispatch.Instance);
|
|
editor.Add(test.ViewPort);
|
|
|
|
editor.Find.Execute("\"abc\"");
|
|
|
|
var results = (SearchResultsViewPort)editor[1];
|
|
Assert.Equal(5, results.ResultCount);
|
|
Assert.Equal(2, results.ChildViewCount);
|
|
Assert.InRange(results.MaximumScroll, 5, 12);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("POKéBALL", "POKéBALL")]
|
|
[InlineData("POKéBALL", "POKeBALL")]
|
|
[InlineData("POKeBALL", "POKéBALL")]
|
|
[InlineData("POKeBALL", "POKeBALL")]
|
|
public void Text_SearchTerm_Find(string text, string searchTerm) {
|
|
var test = new BaseViewModelTestClass();
|
|
var bytes = PCSString.Convert(text).ToArray();
|
|
Array.Copy(bytes, test.Model.RawData, bytes.Length);
|
|
|
|
var results = test.ViewPort.Find(searchTerm);
|
|
|
|
Assert.Single(results);
|
|
}
|
|
|
|
private static StubDataModel SelfEqualStub() {
|
|
var model = new StubDataModel();
|
|
model.Equals = input => input == model;
|
|
return model;
|
|
}
|
|
}
|
|
}
|