diff --git a/src/Components/Input.re b/src/Components/Input.re
index 47833b6142..82d681bb8a 100644
--- a/src/Components/Input.re
+++ b/src/Components/Input.re
@@ -70,45 +70,15 @@ let getStringParts = (index, str) => {
};
};
-let getSafeStringBounds = (str, cursorPosition, change) => {
- let nextPosition = cursorPosition + change;
- let currentLength = String.length(str);
- nextPosition > currentLength
- ? currentLength : nextPosition < 0 ? 0 : nextPosition;
-};
-
-let removeCharacterBefore = (word, cursorPosition) => {
- let (startStr, endStr) = getStringParts(cursorPosition, word);
- let nextPosition = getSafeStringBounds(startStr, cursorPosition, -1);
- let newString = Str.string_before(startStr, nextPosition) ++ endStr;
- (newString, nextPosition);
-};
-
-let removeCharacterAfter = (word, cursorPosition) => {
- let (startStr, endStr) = getStringParts(cursorPosition, word);
- let newString =
- startStr
- ++ (
- switch (endStr) {
- | "" => ""
- | _ => Str.last_chars(endStr, String.length(endStr) - 1)
- }
- );
- (newString, cursorPosition);
-};
-
-let addCharacter = (word, char, index) => {
- let (startStr, endStr) = getStringParts(index, word);
- (startStr ++ char ++ endStr, String.length(startStr) + 1);
-};
-
module Constants = {
let cursorWidth = 2;
+ let selectionOpacity = 0.75;
};
module Styles = {
let defaultPlaceholderColor = Colors.grey;
let defaultCursorColor = Colors.black;
+ let defaultSelectionColor = Color.hex("#42557b");
let default =
Style.[
@@ -125,11 +95,12 @@ let%component make =
~style=Styles.default,
~placeholderColor=Styles.defaultPlaceholderColor,
~cursorColor=Styles.defaultCursorColor,
+ ~selectionColor=Styles.defaultSelectionColor,
~placeholder="",
~prefix="",
~isFocused,
~value,
- ~cursorPosition,
+ ~selection: Selection.t,
~onClick,
(),
) => {
@@ -175,6 +146,12 @@ let%component make =
transform(Transform.[TranslateX(float(offset))]),
];
+ let selection = offset => [
+ position(`Absolute),
+ marginTop(2),
+ transform(Transform.[TranslateX(float(offset))]),
+ ];
+
let textContainer = [flexGrow(1), overflow(`Hidden)];
let text = [
@@ -205,7 +182,7 @@ let%component make =
let%hook () =
Hooks.effect(
- If((!=), (value, cursorPosition, isFocused)),
+ If((!=), (value, selection, isFocused)),
() => {
resetCursor();
None;
@@ -214,7 +191,7 @@ let%component make =
let () = {
let cursorOffset =
- measureTextWidth(String.sub(displayValue, 0, cursorPosition))
+ measureTextWidth(String.sub(displayValue, 0, selection.focus))
|> int_of_float;
switch (Option.bind(textRef^, r => r#getParent())) {
@@ -265,9 +242,10 @@ let%component make =
| Some(node) =>
let offset =
int_of_float(event.mouseX) - offsetLeft(node) + scrollOffset^;
- let cursorPosition = indexNearestOffset(offset);
+ let nearestOffset = indexNearestOffset(offset);
+ let selection = Selection.collapsed(~text=value, nearestOffset);
resetCursor();
- onClick(cursorPosition);
+ onClick(selection);
| None => ()
};
@@ -275,7 +253,7 @@ let%component make =
let cursor = () => {
let (startStr, _) =
- getStringParts(cursorPosition + String.length(prefix), displayValue);
+ getStringParts(selection.focus + String.length(prefix), displayValue);
let textWidth = measureTextWidth(startStr) |> int_of_float;
@@ -292,6 +270,36 @@ let%component make =
;
};
+ let selectionView = () =>
+ if (Selection.isCollapsed(selection)) {
+ React.empty;
+ } else {
+ let startOffset = Selection.offsetLeft(selection);
+ let endOffset = Selection.offsetRight(selection);
+
+ let (beginnigStartStr, _) =
+ getStringParts(startOffset + String.length(prefix), displayValue);
+ let beginningTextWidth =
+ measureTextWidth(beginnigStartStr) |> int_of_float;
+ let startOffset = beginningTextWidth - scrollOffset^;
+
+ let (endingStartStr, _) =
+ getStringParts(endOffset + String.length(prefix), displayValue);
+ let endingTextWidth = measureTextWidth(endingStartStr) |> int_of_float;
+ let endOffset = endingTextWidth - scrollOffset^;
+ let width = endOffset - startOffset + Constants.cursorWidth;
+
+
+
+ int_of_float}
+ color=selectionColor
+ />
+
+ ;
+ };
+
let text = () =>
textRef := Some(node)}
@@ -302,6 +310,7 @@ let%component make =
+
diff --git a/src/Components/InputModel.re b/src/Components/InputModel.re
index 2383163c99..dc79ca2e44 100644
--- a/src/Components/InputModel.re
+++ b/src/Components/InputModel.re
@@ -1,6 +1,34 @@
open Oni_Core;
open Utility;
+let wordSeparators = " ./\\()\"'-:,.;<>~!@#$%^&*|+=[]{}`~?";
+
+let separatorOnIndexExn = (index, text) => {
+ String.contains(wordSeparators, text.[index]);
+};
+
+let findNextWordBoundary = (text, focus) => {
+ let finalIndex = String.length(text);
+ let index = ref(min(focus + 1, finalIndex));
+
+ while (index^ < finalIndex && !separatorOnIndexExn(index^, text)) {
+ index := index^ + 1;
+ };
+
+ index^;
+};
+
+let findPrevWordBoundary = (text, focus) => {
+ let finalIndex = 0;
+ let index = ref(max(focus - 1, finalIndex));
+
+ while (index^ > finalIndex && !separatorOnIndexExn(index^ - 1, text)) {
+ index := index^ - 1;
+ };
+
+ index^;
+};
+
let slice = (~start=0, ~stop=?, str) => {
let length = String.length(str);
let start = IntEx.clamp(~lo=0, ~hi=length, start);
@@ -28,13 +56,132 @@ let add = (~at as index, insert, text) => (
index + String.length(insert),
);
-let handleInput = (~text, ~cursorPosition) =>
- fun
- | "" => (text, max(0, cursorPosition - 1))
- | "" => (text, min(String.length(text), cursorPosition + 1))
- | "" => removeBefore(cursorPosition, text)
- | "" => removeAfter(cursorPosition, text)
- | "" => (text, 0)
- | "" => (text, String.length(text))
- | key when String.length(key) == 1 => add(~at=cursorPosition, key, text)
- | _ => (text, cursorPosition);
+let removeCharBefore = (text, selection: Selection.t) => {
+ let (textSlice, _) = removeBefore(selection.focus, text);
+
+ (
+ textSlice,
+ Selection.offsetLeft(selection)
+ - 1
+ |> Selection.collapsed(~text=textSlice),
+ );
+};
+
+let removeSelection = (text, selection) => {
+ let (textSlice, focus) =
+ removeAfter(
+ Selection.offsetLeft(selection),
+ text,
+ ~count=Selection.length(selection),
+ );
+
+ (textSlice, Selection.collapsed(~text=textSlice, focus));
+};
+
+let removeCharAfter = (text, selection: Selection.t) => {
+ let (textSlice, focus) = removeAfter(selection.focus, text);
+
+ (textSlice, Selection.collapsed(~text=textSlice, focus));
+};
+
+let collapsePrevWord = (text, selection: Selection.t) => {
+ let newSelection =
+ selection.focus
+ |> findPrevWordBoundary(text)
+ |> Selection.collapsed(~text);
+
+ (text, newSelection);
+};
+
+let collapseNextWord = (text, selection: Selection.t) => {
+ let newSelection =
+ selection.focus
+ |> findNextWordBoundary(text)
+ |> Selection.collapsed(~text);
+
+ (text, newSelection);
+};
+
+let extendPrevWord = (text, selection: Selection.t) => {
+ let newSelection =
+ selection.focus
+ |> findPrevWordBoundary(text)
+ |> Selection.extend(~text, ~selection);
+
+ (text, newSelection);
+};
+
+let extendNextWord = (text, selection: Selection.t) => {
+ let newSelection =
+ selection.focus
+ |> findNextWordBoundary(text)
+ |> Selection.extend(~text, ~selection);
+
+ (text, newSelection);
+};
+
+let addCharacter = (key, text, selection: Selection.t) => {
+ let (newText, focus) = add(~at=selection.focus, key, text);
+
+ (newText, Selection.collapsed(~text=newText, focus));
+};
+
+let replacesSelection = (key, text, selection: Selection.t) => {
+ let (textSlice, selectionSlice) = removeSelection(text, selection);
+ let (newText, focus) = add(~at=selectionSlice.focus, key, textSlice);
+
+ (newText, Selection.collapsed(~text=newText, focus));
+};
+
+let handleInput = (~text, ~selection: Selection.t, key) => {
+ switch (key, Selection.isCollapsed(selection)) {
+ | ("", true) => (
+ text,
+ Selection.offsetLeft(selection) - 1 |> Selection.collapsed(~text),
+ )
+ | ("", false) => (
+ text,
+ Selection.offsetLeft(selection) |> Selection.collapsed(~text),
+ )
+ | ("", true) => (
+ text,
+ Selection.offsetLeft(selection) + 1 |> Selection.collapsed(~text),
+ )
+ | ("", false) => (
+ text,
+ Selection.offsetRight(selection) |> Selection.collapsed(~text),
+ )
+ | ("", true) => removeCharBefore(text, selection)
+ | ("", false) => removeSelection(text, selection)
+ | ("", true) => removeCharAfter(text, selection)
+ | ("", false) => removeSelection(text, selection)
+ | ("", _) => (text, Selection.collapsed(~text, 0))
+ | ("", _) => (text, Selection.collapsed(~text, String.length(text)))
+ | ("", _) => (
+ text,
+ selection.focus - 1 |> Selection.extend(~text, ~selection),
+ )
+ | ("", _) => (
+ text,
+ selection.focus + 1 |> Selection.extend(~text, ~selection),
+ )
+ | ("", _) => collapsePrevWord(text, selection)
+ | ("", _) => collapseNextWord(text, selection)
+ | ("", _) => (text, Selection.extend(~text, ~selection, 0))
+ | ("", _) => (
+ text,
+ Selection.extend(~text, ~selection, String.length(text)),
+ )
+ | ("", _) => extendPrevWord(text, selection)
+ | ("", _) => extendNextWord(text, selection)
+ | ("", _) => (
+ text,
+ Selection.create(~text, ~anchor=0, ~focus=String.length(text)),
+ )
+ | (key, true) when String.length(key) == 1 =>
+ addCharacter(key, text, selection)
+ | (key, false) when String.length(key) == 1 =>
+ replacesSelection(key, text, selection)
+ | (_, _) => (text, selection)
+ };
+};
diff --git a/src/Components/Selection.re b/src/Components/Selection.re
new file mode 100644
index 0000000000..cd566e91e0
--- /dev/null
+++ b/src/Components/Selection.re
@@ -0,0 +1,42 @@
+open Utility;
+
+[@deriving show({with_path: false})]
+type t = {
+ anchor: int,
+ focus: int,
+};
+
+let initial: t = {anchor: 0, focus: 0};
+
+let create = (~text: string, ~anchor: int, ~focus: int): t => {
+ let safeOffset = IntEx.clamp(~lo=0, ~hi=String.length(text));
+
+ let safeAnchor = safeOffset(anchor);
+ let safeFocus = safeOffset(focus);
+
+ {anchor: safeAnchor, focus: safeFocus};
+};
+
+let length = (selection: t): int => {
+ abs(selection.focus - selection.anchor);
+};
+
+let offsetLeft = (selection: t): int => {
+ min(selection.focus, selection.anchor);
+};
+
+let offsetRight = (selection: t): int => {
+ max(selection.focus, selection.anchor);
+};
+
+let isCollapsed = (selection: t): bool => {
+ selection.anchor == selection.focus;
+};
+
+let collapsed = (~text: string, offset: int): t => {
+ create(~text, ~anchor=offset, ~focus=offset);
+};
+
+let extend = (~text: string, ~selection: t, offset: int): t => {
+ create(~text, ~anchor=selection.anchor, ~focus=offset);
+};
diff --git a/src/Components/Selection.rei b/src/Components/Selection.rei
new file mode 100644
index 0000000000..78cb5b86cd
--- /dev/null
+++ b/src/Components/Selection.rei
@@ -0,0 +1,18 @@
+[@deriving show({with_path: false})]
+type t =
+ pri {
+ anchor: int,
+ focus: int,
+ };
+
+let initial: t;
+
+let create: (~text: string, ~anchor: int, ~focus: int) => t;
+let length: t => int;
+let offsetLeft: t => int;
+let offsetRight: t => int;
+let isCollapsed: t => bool;
+
+let collapsed: (~text: string, int) => t;
+
+let extend: (~text: string, ~selection: t, int) => t;
diff --git a/src/Feature/SCM/Feature_SCM.re b/src/Feature/SCM/Feature_SCM.re
index 8b728a3a83..2fb1b4dd2f 100644
--- a/src/Feature/SCM/Feature_SCM.re
+++ b/src/Feature/SCM/Feature_SCM.re
@@ -3,6 +3,7 @@ open Utility;
module InputModel = Oni_Components.InputModel;
module ExtHostClient = Oni_Extensions.ExtHostClient;
+module Selection = Oni_Components.Selection;
// MODEL
@@ -21,7 +22,7 @@ type model = {
and inputBox = {
value: string,
- cursorPosition: int,
+ selection: Selection.t,
placeholder: string,
};
@@ -29,7 +30,7 @@ let initial = {
providers: [],
inputBox: {
value: "",
- cursorPosition: 0,
+ selection: Selection.initial,
placeholder: "Do the commit thing!",
},
};
@@ -91,7 +92,7 @@ type msg =
command,
})
| KeyPressed({key: string})
- | InputBoxClicked({cursorPosition: int});
+ | InputBoxClicked({selection: Selection.t});
module Msg = {
let keyPressed = key => KeyPressed({key: key});
@@ -300,10 +301,10 @@ let update = (extHostClient, model, msg) =>
)
| KeyPressed({key}) =>
- let (value, cursorPosition) =
+ let (value, selection) =
InputModel.handleInput(
~text=model.inputBox.value,
- ~cursorPosition=model.inputBox.cursorPosition,
+ ~selection=model.inputBox.selection,
key,
);
@@ -313,7 +314,7 @@ let update = (extHostClient, model, msg) =>
inputBox: {
...model.inputBox,
value,
- cursorPosition,
+ selection,
},
},
Effect(
@@ -330,12 +331,12 @@ let update = (extHostClient, model, msg) =>
),
);
- | InputBoxClicked({cursorPosition}) => (
+ | InputBoxClicked({selection}) => (
{
...model,
inputBox: {
...model.inputBox,
- cursorPosition,
+ selection,
},
},
Focus,
@@ -534,10 +535,12 @@ module Pane = {
style={Styles.input(~font)}
cursorColor=Colors.gray
value={model.inputBox.value}
- cursorPosition={model.inputBox.cursorPosition}
+ selection={model.inputBox.selection}
placeholder={model.inputBox.placeholder}
isFocused
- onClick={pos => dispatch(InputBoxClicked({cursorPosition: pos}))}
+ onClick={selection =>
+ dispatch(InputBoxClicked({selection: selection}))
+ }
/>
{groups
|> List.map(((provider, group)) =>
diff --git a/src/Feature/Search/Feature_Search.re b/src/Feature/Search/Feature_Search.re
index 2e78a2eab0..b7db9ccec0 100644
--- a/src/Feature/Search/Feature_Search.re
+++ b/src/Feature/Search/Feature_Search.re
@@ -8,18 +8,23 @@ open Oni_Components;
type model = {
queryInput: string,
query: string,
- cursorPosition: int,
+ selection: Selection.t,
hits: list(Ripgrep.Match.t),
};
-let initial = {queryInput: "", query: "", cursorPosition: 0, hits: []};
+let initial = {
+ queryInput: "",
+ query: "",
+ selection: Selection.initial,
+ hits: [],
+};
// UPDATE
[@deriving show({with_path: false})]
type msg =
| Input(string)
- | InputClicked(int)
+ | InputClicked(Selection.t)
| Update([@opaque] list(Ripgrep.Match.t))
| Complete;
@@ -29,7 +34,7 @@ type outmsg =
let update = (model, msg) => {
switch (msg) {
| Input(key) =>
- let {queryInput, cursorPosition, _} = model;
+ let {queryInput, selection, _} = model;
let model =
switch (key) {
@@ -41,17 +46,14 @@ let update = (model, msg) => {
}
| _ =>
- let (queryInput, cursorPosition) =
- InputModel.handleInput(~text=queryInput, ~cursorPosition, key);
- {...model, queryInput, cursorPosition};
+ let (queryInput, selection) =
+ InputModel.handleInput(~text=queryInput, ~selection, key);
+ {...model, queryInput, selection};
};
(model, None);
- | InputClicked(cursorPosition) => (
- {...model, cursorPosition},
- Some(Focus),
- )
+ | InputClicked(selection) => ({...model, selection}, Some(Focus))
| Update(items) => ({...model, hits: model.hits @ items}, None)
@@ -174,11 +176,11 @@ let make =
dispatch(InputClicked(pos))}
+ onClick={selection => dispatch(InputClicked(selection))}
/>
diff --git a/src/Feature/Search/Feature_Search.rei b/src/Feature/Search/Feature_Search.rei
index a2bc4a4d5d..c77bdde811 100644
--- a/src/Feature/Search/Feature_Search.rei
+++ b/src/Feature/Search/Feature_Search.rei
@@ -1,6 +1,7 @@
open EditorCoreTypes;
open Oni_Core;
open Revery.UI;
+open Oni_Components;
type model;
@@ -9,7 +10,7 @@ let initial: model;
[@deriving show]
type msg =
| Input(string)
- | InputClicked(int)
+ | InputClicked(Selection.t)
| Update([@opaque] list(Ripgrep.Match.t))
| Complete;
diff --git a/src/Model/Actions.re b/src/Model/Actions.re
index 79b86acd76..7dc8bc8588 100644
--- a/src/Model/Actions.re
+++ b/src/Model/Actions.re
@@ -8,6 +8,7 @@ open EditorCoreTypes;
open Oni_Core;
open Oni_Input;
open Oni_Syntax;
+open Oni_Components;
module Ext = Oni_Extensions;
module ContextMenu = Oni_Components.ContextMenu;
@@ -91,7 +92,7 @@ type t =
| LanguageFeature(LanguageFeatures.action)
| QuickmenuShow(quickmenuVariant)
| QuickmenuInput(string)
- | QuickmenuInputClicked(int)
+ | QuickmenuInputClicked(Selection.t)
| QuickmenuCommandlineUpdated(string, int)
| QuickmenuUpdateRipgrepProgress(progress)
| QuickmenuUpdateFilterProgress([@opaque] array(menuItem), progress)
diff --git a/src/Model/Quickmenu.re b/src/Model/Quickmenu.re
index ac3c76c22f..1ece2ead32 100644
--- a/src/Model/Quickmenu.re
+++ b/src/Model/Quickmenu.re
@@ -1,10 +1,11 @@
open Actions;
+module Selection = Oni_Components.Selection;
type t = {
variant,
query: string,
prefix: option(string),
- cursorPosition: int,
+ selection: Selection.t,
items: array(menuItem),
filterProgress: progress,
ripgrepProgress: progress,
@@ -24,7 +25,7 @@ let defaults = variant => {
variant,
query: "",
prefix: None,
- cursorPosition: 0,
+ selection: Selection.initial,
focused: None,
items: [||],
filterProgress: Complete,
diff --git a/src/Model/Sneak.re b/src/Model/Sneak.re
index c5e567748a..1278615048 100644
--- a/src/Model/Sneak.re
+++ b/src/Model/Sneak.re
@@ -2,6 +2,7 @@ open Revery.Math;
module InputModel = Oni_Components.InputModel;
module StringEx = Oni_Core.Utility.StringEx;
+module Selection = Oni_Components.Selection;
type callback = unit => unit;
type bounds = unit => option(BoundingBox2d.t);
@@ -84,13 +85,11 @@ module Internal = {
let refine = (characterToAdd: string, sneaks: t) => {
let characterToAdd = String.uppercase_ascii(characterToAdd);
+ let selection =
+ String.length(sneaks.prefix) |> Selection.collapsed(~text=sneaks.prefix);
let (prefix, _) =
- InputModel.handleInput(
- ~text=sneaks.prefix,
- ~cursorPosition=String.length(sneaks.prefix),
- characterToAdd,
- );
+ InputModel.handleInput(~text=sneaks.prefix, ~selection, characterToAdd);
{...sneaks, prefix} |> Internal.applyFilter;
};
diff --git a/src/Store/InputStoreConnector.re b/src/Store/InputStoreConnector.re
index bfda4db8b2..48ca7889a2 100644
--- a/src/Store/InputStoreConnector.re
+++ b/src/Store/InputStoreConnector.re
@@ -6,6 +6,7 @@
open Oni_Core;
open Oni_Input;
+open Oni_Components;
module Model = Oni_Model;
module State = Model.State;
@@ -21,11 +22,12 @@ let conditionsOfState = (state: State.t) => {
let ret: Hashtbl.t(string, bool) = Hashtbl.create(16);
switch (state.quickmenu) {
- | Some({variant, query, cursorPosition, _}) =>
+ | Some({variant, query, selection, _}) =>
Hashtbl.add(ret, "listFocus", true);
Hashtbl.add(ret, "inQuickOpen", true);
- if (cursorPosition == String.length(query)) {
+ if (Selection.isCollapsed(selection)
+ && selection.focus == String.length(query)) {
Hashtbl.add(ret, "quickmenuCursorEnd", true);
};
diff --git a/src/Store/QuickmenuStoreConnector.re b/src/Store/QuickmenuStoreConnector.re
index 722d1ce222..9614ebe05d 100644
--- a/src/Store/QuickmenuStoreConnector.re
+++ b/src/Store/QuickmenuStoreConnector.re
@@ -149,30 +149,30 @@ let start = (themeInfo: ThemeInfo.t) => {
| QuickmenuInput(key) => (
Option.map(
- (Quickmenu.{query, cursorPosition, _} as state) => {
- let (text, cursorPosition) =
- InputModel.handleInput(~text=query, ~cursorPosition, key);
+ (Quickmenu.{query, selection, _} as state) => {
+ let (text, selection) =
+ InputModel.handleInput(~text=query, ~selection, key);
- Quickmenu.{
- ...state,
- query: text,
- cursorPosition,
- focused: Some(0),
- };
+ Quickmenu.{...state, query: text, selection, focused: Some(0)};
},
state,
),
Isolinear.Effect.none,
)
- | QuickmenuInputClicked(cursorPosition) => (
- Option.map(state => Quickmenu.{...state, cursorPosition}, state),
+ | QuickmenuInputClicked(selection) => (
+ Option.map(state => Quickmenu.{...state, selection}, state),
Isolinear.Effect.none,
)
| QuickmenuCommandlineUpdated(text, cursorPosition) => (
Option.map(
- state => Quickmenu.{...state, query: text, cursorPosition},
+ state =>
+ Quickmenu.{
+ ...state,
+ query: text,
+ selection: Selection.collapsed(~text, cursorPosition),
+ },
state,
),
Isolinear.Effect.none,
diff --git a/src/UI/QuickmenuView.re b/src/UI/QuickmenuView.re
index 066c0f8ca7..09325d0394 100644
--- a/src/UI/QuickmenuView.re
+++ b/src/UI/QuickmenuView.re
@@ -53,8 +53,8 @@ module Styles = {
let onFocusedChange = index =>
GlobalContext.current().dispatch(ListFocus(index));
-let onInputClicked = cursorPosition =>
- GlobalContext.current().dispatch(QuickmenuInputClicked(cursorPosition));
+let onInputClicked = selection =>
+ GlobalContext.current().dispatch(QuickmenuInputClicked(selection));
let onSelect = _ => GlobalContext.current().dispatch(ListSelect);
@@ -107,7 +107,7 @@ let make =
ripgrepProgress,
focused,
query,
- cursorPosition,
+ selection,
prefix,
variant,
_,
@@ -161,7 +161,7 @@ let make =
isFocused=true
onClick=onInputClicked
value=query
- cursorPosition
+ selection
/>
;
@@ -184,7 +184,10 @@ let make =
| EditorsPicker => React.empty
| _ =>
}}
-
+ {switch (variant) {
+ | Wildmenu(SearchForward | SearchReverse) => React.empty
+ | _ =>
+ }}
;
diff --git a/test/Components/InputModelTests.re b/test/Components/InputModelTests.re
new file mode 100644
index 0000000000..54a174837b
--- /dev/null
+++ b/test/Components/InputModelTests.re
@@ -0,0 +1,1240 @@
+open TestFramework;
+
+module InputModel = Oni_Components.InputModel;
+module Selection = Oni_Components.Selection;
+
+let testString = "Some interesting. Test. String. Isn't it? Maybe";
+let testStringLength = String.length(testString);
+
+let runInputHandler = (~text=testString, selection, key) => {
+ InputModel.handleInput(~text, ~selection, key);
+};
+
+let collapsedSelection = (~text=testString, position) => {
+ Selection.create(~text, ~anchor=position, ~focus=position);
+};
+
+let notCollapsedSelection = (~text=testString, ~anchor, ~focus, ()) => {
+ Selection.create(~text, ~anchor, ~focus);
+};
+
+describe("handleInputS#handleInput", ({describe, _}) => {
+ describe("When LEFT with no selection", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor left for 1 character", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected = collapsedSelection(3);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor less then 0 position", ({expect}) => {
+ let selection = collapsedSelection(0);
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+ });
+
+ describe("When LEFT with selection", ({test, _}) => {
+ let key = "";
+
+ test(
+ "Moves cursor to the beginning of selection when cursor comes first",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=4, ~focus=2, ());
+ let expected = notCollapsedSelection(~anchor=2, ~focus=2, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to the beginning of selection when cursor comes last",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=2, ~focus=4, ());
+ let expected = notCollapsedSelection(~anchor=2, ~focus=2, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to the beginning of selection when cursor at the beginning",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=2, ~focus=4, ());
+ let expected = notCollapsedSelection(~anchor=2, ~focus=2, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+ });
+
+ describe("When with no selection", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor right for 1 character", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected = collapsedSelection(5);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position more that string length", ({expect}) => {
+ let selection = collapsedSelection(testStringLength);
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+ });
+
+ describe("When RIGHT with selection", ({test, _}) => {
+ let key = "";
+
+ test(
+ "Moves cursor to the end of selection when cursor comes first",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=2, ~focus=4, ());
+ let expected = notCollapsedSelection(~anchor=4, ~focus=4, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to the end of selection when cursor comes last",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=4, ~focus=2, ());
+ let expected = notCollapsedSelection(~anchor=4, ~focus=4, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to the end of selection when cursor at the beginning",
+ ({expect}) => {
+ let selection =
+ notCollapsedSelection(~anchor=testStringLength, ~focus=0, ());
+ let expected =
+ notCollapsedSelection(
+ ~anchor=testStringLength,
+ ~focus=testStringLength,
+ (),
+ );
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+ });
+
+ describe("When with no selection", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor right for 1 character", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected = collapsedSelection(5);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position more that string length", ({expect}) => {
+ let selection = collapsedSelection(testStringLength);
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+ });
+
+ describe("When with no selection", ({test, _}) => {
+ let key = "";
+
+ test("Removes character on the left of cursor", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected = collapsedSelection(3);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(
+ "Som interesting. Test. String. Isn't it? Maybe",
+ );
+ });
+
+ test("Doesn't remove character if cursor at the beginnig", ({expect}) => {
+ let selection = collapsedSelection(0);
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Don't do anything for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", -1);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+ });
+
+ describe("When with with selection", ({test, _}) => {
+ let key = "";
+
+ test("Removes selection when cursor comes first", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=4, ~focus=2, ());
+ let expected = notCollapsedSelection(~anchor=2, ~focus=2, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(
+ "So interesting. Test. String. Isn't it? Maybe",
+ );
+ });
+
+ test("Removes selection when cursor comes last", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=2, ~focus=4, ());
+ let expected = notCollapsedSelection(~anchor=2, ~focus=2, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(
+ "So interesting. Test. String. Isn't it? Maybe",
+ );
+ });
+ });
+
+ describe("When with no selection", ({test, _}) => {
+ let key = "";
+
+ test("Removes character on the right of cursor", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected = collapsedSelection(4);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(
+ "Someinteresting. Test. String. Isn't it? Maybe",
+ );
+ });
+
+ test("Doesn't remove character if cursor at the end", ({expect}) => {
+ let selection = collapsedSelection(testStringLength);
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Don't do anything for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+ });
+
+ describe("When with with selection", ({test, _}) => {
+ let key = "";
+
+ test("Removes selection when cursor comes first", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=4, ~focus=2, ());
+ let expected = notCollapsedSelection(~anchor=2, ~focus=2, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(
+ "So interesting. Test. String. Isn't it? Maybe",
+ );
+ });
+
+ test("Removes selection when cursor comes last", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=2, ~focus=4, ());
+ let expected = notCollapsedSelection(~anchor=2, ~focus=2, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(
+ "So interesting. Test. String. Isn't it? Maybe",
+ );
+ });
+ });
+
+ describe("When HOME with no selection", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to the beginning", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor if it's at the beginning", ({expect}) => {
+ let selection = collapsedSelection(0);
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+ });
+
+ describe("When HOME with selection", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to the beginning and discard selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=12, ~focus=5, ());
+ let expected = notCollapsedSelection(~anchor=0, ~focus=0, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Doesn't move cursor if it's at the beginning and discard selection",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=8, ~focus=0, ());
+ let expected = notCollapsedSelection(~anchor=0, ~focus=0, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+ });
+
+ describe("When END with no selection", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to the end", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor if it's at the end", ({expect}) => {
+ let selection = collapsedSelection(testStringLength);
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+ });
+
+ describe("When END with selection", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to the end and discard selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=11, ~focus=5, ());
+ let expected =
+ notCollapsedSelection(
+ ~anchor=testStringLength,
+ ~focus=testStringLength,
+ (),
+ );
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Doesn't move cursor if it's at the end and discard selection",
+ ({expect}) => {
+ let selection =
+ notCollapsedSelection(~anchor=testStringLength, ~focus=4, ());
+ let expected =
+ notCollapsedSelection(
+ ~anchor=testStringLength,
+ ~focus=testStringLength,
+ (),
+ );
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+ });
+
+ describe("When S-LEFT", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to 1 character left and add selection", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected = notCollapsedSelection(~anchor=4, ~focus=3, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to 1 character left and increase selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=11, ~focus=5, ());
+ let expected = notCollapsedSelection(~anchor=11, ~focus=4, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position when it at the beginning", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=5, ~focus=0, ());
+ let expected = notCollapsedSelection(~anchor=5, ~focus=0, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Doesn't move cursor position when it at the beginning and no selection",
+ ({expect}) => {
+ let selection = collapsedSelection(0);
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+
+ test("Moves cursor to 1 character left and undo selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=4, ~focus=5, ());
+ let expected = collapsedSelection(4);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to 1 character left and decrease selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=4, ~focus=8, ());
+ let expected = notCollapsedSelection(~anchor=4, ~focus=7, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+ });
+
+ describe("When S-RIGHT", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to 1 character right and add selection", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected = notCollapsedSelection(~anchor=4, ~focus=5, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to 1 character right and increase selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=4, ~focus=11, ());
+ let expected = notCollapsedSelection(~anchor=4, ~focus=12, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position when it at the end", ({expect}) => {
+ let selection =
+ notCollapsedSelection(~anchor=5, ~focus=testStringLength, ());
+ let expected =
+ notCollapsedSelection(~anchor=5, ~focus=testStringLength, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Doesn't move cursor position when it at the end and no selection",
+ ({expect}) => {
+ let selection = collapsedSelection(testStringLength);
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+
+ test("Moves cursor to 1 character right and undo selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=6, ~focus=5, ());
+ let expected = collapsedSelection(6);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to 1 character right and decrease selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=8, ~focus=3, ());
+ let expected = notCollapsedSelection(~anchor=8, ~focus=4, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+ });
+
+ describe("When S-HOME", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to the beginning and add selection", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected = notCollapsedSelection(~anchor=4, ~focus=0, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Moves cursor to the beginning and increase selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=7, ~focus=4, ());
+ let expected = notCollapsedSelection(~anchor=7, ~focus=0, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position when it at the beginning", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=5, ~focus=0, ());
+ let expected = notCollapsedSelection(~anchor=5, ~focus=0, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Doesn't move cursor position when it at the beginning and no selection",
+ ({expect}) => {
+ let selection = collapsedSelection(0);
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+
+ test("Moves cursor to the beginning and undo selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=0, ~focus=6, ());
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Moves cursor to the beginning and decrease selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=3, ~focus=8, ());
+ let expected = notCollapsedSelection(~anchor=3, ~focus=0, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+ });
+
+ describe("When S-END", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to the end and add selection", ({expect}) => {
+ let selection = collapsedSelection(4);
+ let expected =
+ notCollapsedSelection(~anchor=4, ~focus=testStringLength, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Moves cursor to the end and increase selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=4, ~focus=8, ());
+ let expected =
+ notCollapsedSelection(~anchor=4, ~focus=testStringLength, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position when it at the end", ({expect}) => {
+ let selection =
+ notCollapsedSelection(~anchor=5, ~focus=testStringLength, ());
+ let expected =
+ notCollapsedSelection(~anchor=5, ~focus=testStringLength, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Doesn't move cursor position when it at the and and no selection",
+ ({expect}) => {
+ let selection = collapsedSelection(testStringLength);
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(0);
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+
+ test("Moves cursor to the end and undo selection", ({expect}) => {
+ let selection =
+ notCollapsedSelection(~anchor=testStringLength, ~focus=6, ());
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Moves cursor to the end and decrease selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=7, ~focus=3, ());
+ let expected =
+ notCollapsedSelection(~anchor=7, ~focus=testStringLength, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+ });
+
+ describe("When S-C-LEFT", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to previous word boundary", ({expect}) => {
+ let selection = collapsedSelection(10);
+ let expected = notCollapsedSelection(~anchor=10, ~focus=5, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Moves cursor to beginning", ({expect}) => {
+ let selection = collapsedSelection(3);
+ let expected = notCollapsedSelection(~anchor=3, ~focus=0, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position when it at the beginning", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=10, ~focus=0, ());
+ let expected = notCollapsedSelection(~anchor=10, ~focus=0, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Doesn't move cursor position when it at the beginning and no selection",
+ ({expect}) => {
+ let selection = collapsedSelection(0);
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+
+ test(
+ "Moves cursor to the previous word boundary and undo selection",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=5, ~focus=16, ());
+ let expected = notCollapsedSelection(~anchor=5, ~focus=5, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to the previous word boundary and decrease selection",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=11, ~focus=16, ());
+ let expected = notCollapsedSelection(~anchor=11, ~focus=5, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+ });
+
+ describe("When C-LEFT", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to previous word boundary", ({expect}) => {
+ let selection = collapsedSelection(10);
+ let expected = collapsedSelection(5);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Moves cursor to beginning", ({expect}) => {
+ let selection = collapsedSelection(3);
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position when it at the beginning", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=10, ~focus=0, ());
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Doesn't move cursor position when it at the beginning and no selection",
+ ({expect}) => {
+ let selection = collapsedSelection(0);
+ let expected = collapsedSelection(0);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+
+ test(
+ "Moves cursor to the previous word boundary and undo selection",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=5, ~focus=16, ());
+ let expected = collapsedSelection(5);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to the previous word boundary and decrease selection",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=11, ~focus=16, ());
+ let expected = collapsedSelection(5);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+ });
+
+ describe("When S-C-RIGHT", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to next word boundary", ({expect}) => {
+ let selection = collapsedSelection(10);
+ let expected = notCollapsedSelection(~anchor=10, ~focus=16, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Moves cursor to end", ({expect}) => {
+ let selection = collapsedSelection(44);
+ let expected =
+ notCollapsedSelection(~anchor=44, ~focus=testStringLength, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position when it at the end", ({expect}) => {
+ let selection =
+ notCollapsedSelection(~anchor=10, ~focus=testStringLength, ());
+ let expected =
+ notCollapsedSelection(~anchor=10, ~focus=testStringLength, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Doesn't move cursor position when it at the and and no selection",
+ ({expect}) => {
+ let selection = collapsedSelection(testStringLength);
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+
+ test(
+ "Moves cursor to the next word boundary and undo selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=16, ~focus=5, ());
+ let expected = notCollapsedSelection(~anchor=16, ~focus=16, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to the next word boundary and decrease selection",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=10, ~focus=6, ());
+ let expected = notCollapsedSelection(~anchor=10, ~focus=16, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+ });
+
+ describe("When C-RIGHT", ({test, _}) => {
+ let key = "";
+
+ test("Moves cursor to next word boundary", ({expect}) => {
+ let selection = collapsedSelection(10);
+ let expected = collapsedSelection(16);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Moves cursor to end", ({expect}) => {
+ let selection = collapsedSelection(44);
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position when it at the end", ({expect}) => {
+ let selection =
+ notCollapsedSelection(~anchor=10, ~focus=testStringLength, ());
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Doesn't move cursor position when it at the and and no selection",
+ ({expect}) => {
+ let selection = collapsedSelection(testStringLength);
+ let expected = collapsedSelection(testStringLength);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Doesn't move cursor position for blank string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+
+ test(
+ "Moves cursor to the next word boundary and undo selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=16, ~focus=5, ());
+ let expected = collapsedSelection(16);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test(
+ "Moves cursor to the next word boundary and decrease selection",
+ ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=10, ~focus=6, ());
+ let expected = collapsedSelection(16);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+ });
+
+ describe("When ASCII letter when no selection", ({test, _}) => {
+ let key = "F";
+
+ test("Adds character to the beginning", ({expect}) => {
+ let selection = collapsedSelection(0);
+ let expected = collapsedSelection(1);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.string(text).toEqual(
+ "FSome interesting. Test. String. Isn't it? Maybe",
+ );
+ expect.equal(expected, newSelection);
+ });
+
+ test("Adds character to the end", ({expect}) => {
+ let selection = collapsedSelection(testStringLength);
+ let expected =
+ collapsedSelection(~text=testString ++ key, testStringLength + 1);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(
+ "Some interesting. Test. String. Isn't it? MaybeF",
+ );
+ });
+
+ test("Adds character to the cursor position", ({expect}) => {
+ let selection = collapsedSelection(7);
+ let expected = collapsedSelection(8);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(
+ "Some inFteresting. Test. String. Isn't it? Maybe",
+ );
+ });
+ });
+
+ describe("When ASCII letter when with selection", ({test, _}) => {
+ let key = "F";
+
+ test("Replaces character", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=0, ~focus=1, ());
+ let expected = collapsedSelection(1);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(
+ "Fome interesting. Test. String. Isn't it? Maybe",
+ );
+ });
+
+ test("Adds character many characters", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=16, ~focus=4, ());
+ let expected = collapsedSelection(5);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("SomeF. Test. String. Isn't it? Maybe");
+ });
+
+ test("Replaces all string", ({expect}) => {
+ let selection =
+ notCollapsedSelection(~anchor=testStringLength, ~focus=0, ());
+ let expected = collapsedSelection(1);
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("F");
+ });
+ });
+
+ describe("When C-a", ({test, _}) => {
+ let key = "";
+
+ test("Select all when no selection", ({expect}) => {
+ let selection = collapsedSelection(3);
+ let expected =
+ notCollapsedSelection(~anchor=0, ~focus=testStringLength, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Select all when is selection", ({expect}) => {
+ let selection = notCollapsedSelection(~anchor=5, ~focus=24, ());
+ let expected =
+ notCollapsedSelection(~anchor=0, ~focus=testStringLength, ());
+
+ let (text, newSelection) = runInputHandler(selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual(testString);
+ });
+
+ test("Selects nothing with empty string", ({expect}) => {
+ let selection = collapsedSelection(~text="", 0);
+ let expected = collapsedSelection(~text="", 0);
+
+ let (text, newSelection) = runInputHandler(~text="", selection, key);
+
+ expect.equal(expected, newSelection);
+ expect.string(text).toEqual("");
+ });
+ });
+});
diff --git a/test/Components/SelectionTests.re b/test/Components/SelectionTests.re
new file mode 100644
index 0000000000..b7cfdcec52
--- /dev/null
+++ b/test/Components/SelectionTests.re
@@ -0,0 +1,190 @@
+open TestFramework;
+
+module Selection = Oni_Components.Selection;
+
+let testString = "Some Strin";
+let testStringLength = String.length(testString);
+let create = Selection.create(~text=testString);
+
+describe("Selection#initial", ({test, _}) => {
+ test("Returns initial value", ({expect}) => {
+ let result = Selection.initial;
+
+ expect.int(result.anchor).toBe(0);
+ expect.int(result.focus).toBe(0);
+ })
+});
+
+describe("Selection#create", ({test, _}) => {
+ test("Returns valid selection", ({expect}) => {
+ let result = create(~anchor=3, ~focus=3);
+
+ expect.int(result.anchor).toBe(3);
+ expect.int(result.focus).toBe(3);
+ });
+
+ test("Handle different values", ({expect}) => {
+ let result = create(~anchor=3, ~focus=6);
+
+ expect.int(result.anchor).toBe(3);
+ expect.int(result.focus).toBe(6);
+ });
+
+ test("Handle values below 0", ({expect}) => {
+ let result = create(~anchor=-1, ~focus=-3);
+
+ expect.int(result.anchor).toBe(0);
+ expect.int(result.focus).toBe(0);
+ });
+
+ test("Handle above length", ({expect}) => {
+ let result = create(~anchor=70, ~focus=55);
+
+ expect.int(result.anchor).toBe(testStringLength);
+ expect.int(result.focus).toBe(testStringLength);
+ });
+});
+
+describe("Selection#length", ({test, _}) => {
+ test("Returns range when anchor comes first", ({expect}) => {
+ let result = Selection.length(create(~anchor=3, ~focus=5));
+
+ expect.int(result).toBe(2);
+ });
+
+ test("Returns range when anchor comes last", ({expect}) => {
+ let result = Selection.length(create(~anchor=5, ~focus=3));
+
+ expect.int(result).toBe(2);
+ });
+
+ test("Returns 0 for collapsed selection", ({expect}) => {
+ let result = Selection.length(create(~anchor=3, ~focus=3));
+
+ expect.int(result).toBe(0);
+ });
+});
+
+describe("Selection#offsetLeft", ({test, _}) => {
+ test("Returns anchor", ({expect}) => {
+ let result = Selection.offsetLeft(create(~anchor=3, ~focus=5));
+
+ expect.int(result).toBe(3);
+ });
+
+ test("Returns focus", ({expect}) => {
+ let result = Selection.offsetLeft(create(~anchor=5, ~focus=3));
+
+ expect.int(result).toBe(3);
+ });
+
+ test("Returns any", ({expect}) => {
+ let result = Selection.offsetLeft(create(~anchor=3, ~focus=3));
+
+ expect.int(result).toBe(3);
+ });
+});
+
+describe("Selection#offsetRight", ({test, _}) => {
+ test("Returns anchor", ({expect}) => {
+ let result = Selection.offsetRight(create(~anchor=5, ~focus=3));
+
+ expect.int(result).toBe(5);
+ });
+
+ test("Returns focus", ({expect}) => {
+ let result = Selection.offsetRight(create(~anchor=3, ~focus=5));
+
+ expect.int(result).toBe(5);
+ });
+
+ test("Returns any", ({expect}) => {
+ let result = Selection.offsetRight(create(~anchor=3, ~focus=3));
+
+ expect.int(result).toBe(3);
+ });
+});
+
+describe("Selection#isCollapsed", ({test, _}) => {
+ test("Returns true", ({expect}) => {
+ let result = Selection.isCollapsed(create(~anchor=3, ~focus=3));
+
+ expect.bool(result).toBe(true);
+ });
+
+ test("Returns false", ({expect}) => {
+ let result = Selection.isCollapsed(create(~anchor=3, ~focus=7));
+
+ expect.bool(result).toBe(false);
+ });
+});
+
+describe("Selection#collapse", ({test, _}) => {
+ let collapse = Selection.collapsed(~text=testString);
+
+ test("Collapse selection with offset", ({expect}) => {
+ let result = collapse(3);
+
+ expect.int(result.anchor).toBe(3);
+ expect.int(result.focus).toBe(3);
+ });
+
+ test("Collapse selection with offset less then 0", ({expect}) => {
+ let result = collapse(-20);
+
+ expect.int(result.anchor).toBe(0);
+ expect.int(result.focus).toBe(0);
+ });
+
+ test("Collapse selection with offset is more then length", ({expect}) => {
+ let result = collapse(testStringLength + 70);
+
+ expect.int(result.anchor).toBe(testStringLength);
+ expect.int(result.focus).toBe(testStringLength);
+ });
+});
+
+describe("Selection#extend", ({test, _}) => {
+ let extend = Selection.extend(~text=testString);
+
+ test("Extend when selection is collapsed", ({expect}) => {
+ let selection = create(~anchor=3, ~focus=3);
+ let result = extend(~selection, 5);
+
+ expect.int(result.anchor).toBe(3);
+ expect.int(result.focus).toBe(5);
+ });
+
+ test("Extend when selection is not collapsed", ({expect}) => {
+ let selection = create(~anchor=3, ~focus=8);
+ let result = extend(~selection, 5);
+
+ expect.int(result.anchor).toBe(3);
+ expect.int(result.focus).toBe(5);
+ });
+
+ test(
+ "Doesn't extend when selection is not collapsed in offset", ({expect}) => {
+ let selection = create(~anchor=3, ~focus=3);
+ let result = extend(~selection, 3);
+
+ expect.int(result.anchor).toBe(3);
+ expect.int(result.focus).toBe(3);
+ });
+
+ test("Extends when offset is less than 0", ({expect}) => {
+ let selection = create(~anchor=3, ~focus=3);
+ let result = extend(~selection, -3);
+
+ expect.int(result.anchor).toBe(3);
+ expect.int(result.focus).toBe(0);
+ });
+
+ test("Extends when offset is more than length", ({expect}) => {
+ let selection = create(~anchor=3, ~focus=3);
+ let result = extend(~selection, testStringLength + 70);
+
+ expect.int(result.anchor).toBe(3);
+ expect.int(result.focus).toBe(testStringLength);
+ });
+});
diff --git a/test/Components/TestFramework.re b/test/Components/TestFramework.re
new file mode 100644
index 0000000000..7ffdff90b0
--- /dev/null
+++ b/test/Components/TestFramework.re
@@ -0,0 +1,4 @@
+include Rely.Make({
+ let config =
+ Rely.TestFrameworkConfig.initialize({snapshotDir: "", projectDir: ""});
+});
diff --git a/test/Components/dune b/test/Components/dune
new file mode 100644
index 0000000000..52935a37ca
--- /dev/null
+++ b/test/Components/dune
@@ -0,0 +1,5 @@
+(library
+ (name Oni_Components_Test)
+ (library_flags (-linkall -g))
+ (modules (:standard))
+ (libraries Oni2.components rely.lib))
diff --git a/test/OniUnitTestRunner.re b/test/OniUnitTestRunner.re
index 8a8734fa82..98408ae628 100644
--- a/test/OniUnitTestRunner.re
+++ b/test/OniUnitTestRunner.re
@@ -9,3 +9,4 @@ Oni_ExtensionManagement_Test.TestFramework.cli();
Oni_Syntax_Test.TestFramework.cli();
Feature_Editor_Test.TestFramework.cli();
Feature_LanguageSupport_Test.TestFramework.cli();
+Oni_Components_Test.TestFramework.cli();
diff --git a/test/OniUnitTestRunnerCI.re b/test/OniUnitTestRunnerCI.re
index 4413b35116..1f07b41b0b 100644
--- a/test/OniUnitTestRunnerCI.re
+++ b/test/OniUnitTestRunnerCI.re
@@ -46,3 +46,10 @@ Oni_ExtensionManagement_Test.TestFramework.run(
Rely.RunConfig.initialize(),
),
);
+
+Oni_Components_Test.TestFramework.run(
+ Rely.RunConfig.withReporters(
+ [Default, JUnit("./junit.xml")],
+ Rely.RunConfig.initialize(),
+ ),
+);
diff --git a/test/dune b/test/dune
index bfc63440b3..03f6a36ddb 100644
--- a/test/dune
+++ b/test/dune
@@ -6,6 +6,7 @@
(libraries
yojson
Oni_Core_Test
+ Oni_Components_Test
Oni_Extensions_Test
Oni_ExtensionManagement_Test
Oni_Input_Test
@@ -63,6 +64,7 @@
(libraries
yojson
Oni_Core_Test
+ Oni_Components_Test
Oni_ExtensionManagement_Test
Oni_Extensions_Test
Oni_Input_Test