Integrate elm App with Tauri. Can navigate into source directory.

This commit is contained in:
Pascal Le Merrer 2026-01-19 14:13:52 +01:00
commit 94003989ef
50 changed files with 41456 additions and 0 deletions

42
src-elm/elm.json Normal file
View file

@ -0,0 +1,42 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"ChristophP/elm-mark": "2.0.4",
"Gizra/elm-keyboard-event": "1.0.1",
"NoRedInk/elm-json-decode-pipeline": "1.0.1",
"SwiftsNamesake/proper-keyboard": "4.0.0",
"basti1302/elm-human-readable-filesize": "1.2.0",
"carwow/elm-slider": "11.1.6",
"dasch/levenshtein": "1.0.3",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.1",
"elm/json": "1.1.4",
"elm/regex": "1.0.0",
"elm/time": "1.0.0",
"elm-community/list-extra": "8.7.0",
"elm-community/string-extra": "4.0.1",
"elm-explorations/test": "2.2.0",
"rtfeldman/elm-iso8601-date-strings": "1.1.4",
"z5h/jaro-winkler": "1.0.2"
},
"indirect": {
"debois/elm-dom": "1.3.0",
"elm/bytes": "1.0.8",
"elm/parser": "1.1.0",
"elm/random": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.5",
"myrho/elm-round": "1.0.5"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

11645
src-elm/index.html Normal file

File diff suppressed because it is too large Load diff

3068
src-elm/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

8
src-elm/package.json Normal file
View file

@ -0,0 +1,8 @@
{
"devDependencies": {
"elm-json": "^0.2.13",
"elm-live": "^4.0.2",
"elm-review": "^2.13.5",
"elm-test": "^0.19.1-revision17"
}
}

49
src-elm/review/elm.json Normal file
View file

@ -0,0 +1,49 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"SiriusStarr/elm-review-no-unsorted": "1.1.2",
"elm/core": "1.0.5",
"elm/json": "1.1.3",
"elm/project-metadata-utils": "1.0.2",
"jfmengels/elm-review": "2.8.1",
"jfmengels/elm-review-code-style": "1.0.0",
"jfmengels/elm-review-common": "1.2.1",
"jfmengels/elm-review-debug": "1.0.6",
"jfmengels/elm-review-documentation": "2.0.1",
"jfmengels/elm-review-simplify": "2.0.16",
"jfmengels/elm-review-unused": "1.1.22",
"stil4m/elm-syntax": "7.2.9"
},
"indirect": {
"avh4/elm-fifo": "1.0.4",
"elm/html": "1.0.0",
"elm/parser": "1.1.0",
"elm/random": "1.0.0",
"elm/regex": "1.0.0",
"elm/time": "1.0.0",
"elm/virtual-dom": "1.0.3",
"elm-community/dict-extra": "2.4.0",
"elm-community/graph": "6.0.0",
"elm-community/intdict": "3.0.0",
"elm-community/list-extra": "8.6.0",
"elm-community/maybe-extra": "5.3.0",
"elm-community/result-extra": "2.4.0",
"elm-community/string-extra": "4.0.1",
"elm-explorations/test": "1.2.2",
"miniBill/elm-unicode": "1.0.2",
"rtfeldman/elm-hex": "1.0.0",
"stil4m/structured-writer": "1.0.3"
}
},
"test-dependencies": {
"direct": {
"elm-explorations/test": "1.2.2"
},
"indirect": {}
}
}

View file

@ -0,0 +1,77 @@
module ReviewConfig exposing (config)
{-| Do not rename the ReviewConfig module or the config function, because
`elm-review` will look for these.
To add packages that contain rules, add them to this review project using
`elm install author/packagename`
when inside the directory containing this file.
-}
import Docs.ReviewAtDocs
import NoDebug.Log
import NoDebug.TodoOrToString
import NoExposingEverything
import NoImportingEverything
import NoMissingTypeAnnotation
import NoMissingTypeAnnotationInLetIn
import NoMissingTypeExpose
import NoPrematureLetComputation
import NoSimpleLetBody
import NoUnsortedCases
import NoUnsortedLetDeclarations
import NoUnsortedRecords
import NoUnsortedTopLevelDeclarations
import NoUnused.CustomTypeConstructorArgs
import NoUnused.CustomTypeConstructors
import NoUnused.Dependencies
import NoUnused.Exports
import NoUnused.Modules
import NoUnused.Parameters
import NoUnused.Patterns
import NoUnused.Variables
import Review.Rule as Rule exposing (Rule)
import Simplify
config : List Rule
config =
[ Docs.ReviewAtDocs.rule
, NoDebug.Log.rule
, NoDebug.TodoOrToString.rule
|> Rule.ignoreErrorsForDirectories [ "tests/" ]
, NoExposingEverything.rule
, NoImportingEverything.rule []
, NoMissingTypeAnnotation.rule
, NoMissingTypeAnnotationInLetIn.rule
, NoMissingTypeExpose.rule
, NoSimpleLetBody.rule
, NoPrematureLetComputation.rule
, NoUnused.CustomTypeConstructors.rule []
, NoUnused.CustomTypeConstructorArgs.rule
, NoUnused.Dependencies.rule
, NoUnused.Exports.rule
, NoUnused.Modules.rule
, NoUnused.Parameters.rule
, NoUnused.Patterns.rule
, NoUnused.Variables.rule
, NoUnsortedCases.rule NoUnsortedCases.defaults
, NoUnsortedLetDeclarations.rule
(NoUnsortedLetDeclarations.sortLetDeclarations
|> NoUnsortedLetDeclarations.alphabetically
)
, NoUnsortedRecords.rule
(NoUnsortedRecords.defaults
|> NoUnsortedRecords.reportAmbiguousRecordsWithoutFix
)
, NoUnsortedTopLevelDeclarations.rule
(NoUnsortedTopLevelDeclarations.sortTopLevelDeclarations
|> NoUnsortedTopLevelDeclarations.portsFirst
|> NoUnsortedTopLevelDeclarations.exposedOrderWithPrivateLast
|> NoUnsortedTopLevelDeclarations.alphabetically
)
, Simplify.rule Simplify.defaults
]

254
src-elm/src/File.elm Normal file
View file

@ -0,0 +1,254 @@
module File exposing (File, FileStatus(..), defaultDir, extendSelectionToNext, extendSelectionToPrevious, fileDecoder, selectNext, selectPrevious, selectSimilar, toggleSelectionStatus, withName, withParentPath, withStatus)
import Iso8601
import Json.Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (hardcoded, required)
import List.Extra
import StringComparison exposing (isSimilarityLevelGreaterThan)
import Time exposing (Posix, millisToPosix)
type alias File =
{ isDir : Bool
, mode : Int
, modTime : Posix
, name : String
, parentPath : String
, satisfiesFilter : Bool
, size : Int
, status : FileStatus
}
type FileStatus
= Unselected
| Edited
| Selected
| SelectedForDeletion
defaultDir : File
defaultDir =
{ isDir = True
, mode = 777
, modTime = millisToPosix 0
, name = ""
, parentPath = ""
, satisfiesFilter = False
, size = 0
, status = Unselected
}
extendSelectionToNext : List File -> List File
extendSelectionToNext files =
let
( isAtLeastOneFileSelected, updatedFiles ) =
extendSelection List.Extra.mapAccuml files
in
if isAtLeastOneFileSelected then
updatedFiles
else
updatedFiles
|> selectLast
extendSelectionToPrevious : List File -> List File
extendSelectionToPrevious files =
let
( isAtLeastOneFileSelected, updatedFiles ) =
extendSelection List.Extra.mapAccumr files
in
if isAtLeastOneFileSelected then
updatedFiles
else
updatedFiles
|> selectLast
fileDecoder : Decoder File
fileDecoder =
Json.Decode.succeed File
|> required "IsDir" Json.Decode.bool
|> required "Mode" Json.Decode.int
|> required "ModTime" Iso8601.decoder
|> required "Name" Json.Decode.string
|> required "DirPath" Json.Decode.string
|> hardcoded False
|> required "Size" Json.Decode.int
|> hardcoded Unselected
selectNext : List File -> List File
selectNext files =
let
( isAtLeastOneFileSelected, updatedFiles ) =
selectNextOrPrevious List.Extra.mapAccuml files
in
if isAtLeastOneFileSelected then
updatedFiles
else
updatedFiles
|> selectLast
selectPrevious : List File -> List File
selectPrevious files =
let
( isAtLeastOneFileSelected, updatedFiles ) =
selectNextOrPrevious List.Extra.mapAccumr files
in
if isAtLeastOneFileSelected then
updatedFiles
else
updatedFiles
|> selectFirst
{- selects files whose name have a high level of similarity -}
selectSimilar : File -> Int -> List File -> List File
selectSimilar referenceFile minSimilarity files =
List.map (selectIfSimilar referenceFile minSimilarity) files
toggleSelectionStatus : File -> File
toggleSelectionStatus file =
case file.status of
Unselected ->
{ file | status = Selected }
Edited ->
{ file | status = Selected }
Selected ->
{ file | status = Unselected }
SelectedForDeletion ->
-- TODO Remove from the list of files selected for deletion?
{ file | status = Selected }
withName : String -> File -> File
withName name file =
{ file | name = name }
withParentPath : String -> File -> File
withParentPath path file =
{ file | parentPath = path }
withStatus : FileStatus -> File -> File
withStatus fileStatus file =
{ file | status = fileStatus }
extendSelection :
((SelectionAccumulator -> File -> ( SelectionAccumulator, File )) -> SelectionAccumulator -> List File -> ( SelectionAccumulator, List File ))
-> List File
-> ( Bool, List File )
extendSelection visit files =
let
( finalAccumulator, updatedFiles ) =
visit
(\acc file ->
let
newAcc : SelectionAccumulator
newAcc =
{ acc | isPreviousSelected = file.status == Selected }
in
if acc.isPreviousSelected then
( { newAcc | selectedCount = acc.selectedCount + 1 }
, { file | status = Selected }
)
else
( newAcc, file )
)
initialAccumulator
files
initialAccumulator : SelectionAccumulator
initialAccumulator =
{ isPreviousSelected = False
, selectedCount = 0
}
isAtLeastOneFileSelected : Bool
isAtLeastOneFileSelected =
finalAccumulator.selectedCount > 0
in
( isAtLeastOneFileSelected, updatedFiles )
selectFirst : List File -> List File
selectFirst files =
List.Extra.updateAt 0 (\f -> { f | status = Selected }) files
selectIfSimilar : File -> Int -> File -> File
selectIfSimilar referenceFile minSimilarity file =
if
(file == referenceFile)
|| isSimilarityLevelGreaterThan referenceFile.name file.name minSimilarity
then
{ file | status = Selected }
else
{ file | status = Unselected }
selectLast : List File -> List File
selectLast files =
List.Extra.updateAt (List.length files - 1) (\f -> { f | status = Selected }) files
selectNextOrPrevious :
((SelectionAccumulator -> File -> ( SelectionAccumulator, File )) -> SelectionAccumulator -> List File -> ( SelectionAccumulator, List File ))
-> List File
-> ( Bool, List File )
selectNextOrPrevious visit files =
let
( finalAccumulator, updatedFiles ) =
visit
(\acc file ->
let
newAcc : SelectionAccumulator
newAcc =
{ acc | isPreviousSelected = file.status == Selected }
in
if acc.isPreviousSelected then
( { newAcc | selectedCount = acc.selectedCount + 1 }
, { file | status = Selected }
)
else
( newAcc, { file | status = Unselected } )
)
initialAccumulator
files
initialAccumulator : SelectionAccumulator
initialAccumulator =
{ isPreviousSelected = False
, selectedCount = 0
}
isAtLeastOneFileSelected : Bool
isAtLeastOneFileSelected =
finalAccumulator.selectedCount > 0
in
( isAtLeastOneFileSelected, updatedFiles )
type alias SelectionAccumulator =
{ isPreviousSelected : Bool
, selectedCount : Int
}

2619
src-elm/src/Main.elm Normal file

File diff suppressed because it is too large Load diff

48
src-elm/src/Pattern.elm Normal file
View file

@ -0,0 +1,48 @@
module Pattern exposing (Pattern, Token(..), fromString, toRegexp)
import Regex exposing (Regex)
type alias Pattern =
List Token
type Token
= RawString String
| Joker
fromString : String -> List Token
fromString string =
string
|> String.split "*"
|> List.map escapeSpecialChars
|> List.map RawString
|> List.intersperse Joker
|> List.filter (\t -> t /= RawString "")
toRegexp : Pattern -> Maybe Regex
toRegexp pattern =
pattern
|> List.map
(\token ->
case token of
RawString string ->
"(" ++ string ++ ")"
Joker ->
"(.*?)"
)
|> String.concat
|> Regex.fromString
escapeSpecialChars : String -> String
escapeSpecialChars string =
string
|> String.replace "." "\\."
|> String.replace "[" "\\["
|> String.replace "]" "\\]"
|> String.replace "(" "\\("
|> String.replace ")" "\\)"

View file

@ -0,0 +1,53 @@
module StringComparison exposing (..)
import List.Extra
isSimilarityLevelGreaterThan : String -> String -> Int -> Bool
isSimilarityLevelGreaterThan string1 string2 threshold =
let
len1 =
String.length string1
len2 =
String.length string2
chars1 =
string1
|> String.padRight (len2 - len1) ' '
|> String.toList
chars2 =
string2
|> String.padRight (len1 - len2) ' '
|> String.toList
charList =
List.Extra.zip chars1 chars2
similarCharCount =
List.foldl compareChars 0 charList
in
similarCharCount >= threshold
compareChars : ( Char, Char ) -> Int -> Int
compareChars ( char1, char2 ) currentLevel =
if char1 == char2 then
currentLevel + 1
else
currentLevel
--compareChars : List Char -> List Char -> Int -> Int
--compareChars chars1 chars2 currentLevel =
-- if List.isEmpty chars1 || List.isEmpty chars2 then
-- currentLevel
--
-- else if List.head chars1 == List.head chars2 then
-- compareChars (List.drop 1 chars1) (List.drop 1 chars2) (currentLevel + 1)
--
-- else
-- compareChars (List.drop 1 chars1) (List.drop 1 chars2) currentLevel

250
src-elm/tests/FileTest.elm Normal file
View file

@ -0,0 +1,250 @@
module FileTest exposing (suite)
import Expect
import File exposing (File, FileStatus(..), extendSelectionToNext, extendSelectionToPrevious, selectNext, selectPrevious, selectSimilar, withStatus)
import Fixtures exposing (filteredDir1, filteredDir2, filteredDir3, filteredDir4, filteredDir5, filteredDir6, filteredDir7, filteredDir8)
import Iso8601
import Json.Decode exposing (Decoder, decodeString)
import Test exposing (Test, describe, test)
import Time exposing (millisToPosix)
suite : Test
suite =
describe "File module"
[ test "selectNext selects the next file" <|
\_ ->
let
actual : List File
actual =
selectNext
[ filteredDir1
, filteredDir2 |> withStatus Selected
, filteredDir3
, filteredDir4
, filteredDir5
]
expected : List File
expected =
[ filteredDir1
, filteredDir2
, filteredDir3 |> withStatus Selected
, filteredDir4
, filteredDir5
]
in
Expect.equal expected actual
, test "selectNext does nothing when the currently selected file is the last" <|
\_ ->
let
actual : List File
actual =
selectNext expected
expected : List File
expected =
[ filteredDir1
, filteredDir2
, filteredDir3
, filteredDir4
, filteredDir5 |> withStatus Selected
]
in
Expect.equal expected actual
, test "selectNext selects the last file in a list when none is selected" <|
\_ ->
let
actual : List File
actual =
selectNext
[ filteredDir1
, filteredDir2
, filteredDir3
, filteredDir4
, filteredDir5
]
expected : List File
expected =
[ filteredDir1
, filteredDir2
, filteredDir3
, filteredDir4
, filteredDir5 |> withStatus Selected
]
in
Expect.equal expected actual
, test "selectPrevious selects the previous file in a list when there is one" <|
\_ ->
let
actual : List File
actual =
selectPrevious
[ filteredDir1
, filteredDir2
, filteredDir3
, filteredDir4 |> withStatus Selected
, filteredDir5
]
expected : List File
expected =
[ filteredDir1
, filteredDir2
, filteredDir3 |> withStatus Selected
, filteredDir4
, filteredDir5
]
in
Expect.equal expected actual
, test "selectPrevious does nothing when the currently selected file is the first" <|
\_ ->
let
actual : List File
actual =
selectPrevious expected
expected : List File
expected =
[ filteredDir1 |> withStatus Selected
, filteredDir2
, filteredDir3
, filteredDir4
, filteredDir5
]
in
Expect.equal expected actual
, test "selectPrevious selects the first file in a list when none is selected" <|
\_ ->
let
actual : List File
actual =
selectPrevious
[ filteredDir1
, filteredDir2
, filteredDir3
, filteredDir4
, filteredDir5
]
expected : List File
expected =
[ filteredDir1 |> withStatus Selected
, filteredDir2
, filteredDir3
, filteredDir4
, filteredDir5
]
in
Expect.equal expected actual
, test "extendSelectionToNext selects the next file after the first selected" <|
\_ ->
let
actual : List File
actual =
extendSelectionToNext
[ filteredDir1
, filteredDir2 |> withStatus Selected
, filteredDir3
, filteredDir4
, filteredDir5
]
expected : List File
expected =
[ filteredDir1
, filteredDir2 |> withStatus Selected
, filteredDir3 |> withStatus Selected
, filteredDir4
, filteredDir5
]
in
Expect.equal expected actual
, test "extendSelectionToPrevious selects the file before the first selected" <|
\_ ->
let
actual : List File
actual =
extendSelectionToPrevious
[ filteredDir1
, filteredDir2
, filteredDir3 |> withStatus Selected
, filteredDir4
, filteredDir5
]
expected : List File
expected =
[ filteredDir1
, filteredDir2 |> withStatus Selected
, filteredDir3 |> withStatus Selected
, filteredDir4
, filteredDir5
]
in
Expect.equal expected actual
, test "selectSimilar selects the files with a name looking like the given one" <|
\_ ->
let
actual : List File
actual =
selectSimilar
filteredDir6
10
[ filteredDir1
, filteredDir6 |> withStatus Selected
, filteredDir2
, filteredDir7
, filteredDir3
, filteredDir8
, filteredDir4 |> withStatus Selected
, filteredDir5
]
expected : List File
expected =
[ filteredDir1
, filteredDir6 |> withStatus Selected
, filteredDir2
, filteredDir7 |> withStatus Selected
, filteredDir3
, filteredDir8 |> withStatus Selected
, filteredDir4
, filteredDir5
]
in
Expect.equal expected actual
, test "filerDecoder is able to decode a file descriptor" <|
\_ ->
let
actual : File
actual =
"""
{
"DirPath": "/Users/pascal",
"IsDir": true,
"ModTime": "2025-12-16T19:43:08.307Z",
"Mode": 16832,
"Name": "Music",
"Size": 256
}
"""
|> decodeString File.fileDecoder
|> Result.toMaybe
|> Maybe.withDefault Fixtures.dir1
expected : File
expected =
{ parentPath = "/Users/pascal"
, isDir = True
, modTime = millisToPosix 1765914188307
, mode = 16832
, name = "Music"
, satisfiesFilter = False
, status = Unselected
, size = 256
}
in
Expect.equal expected actual
]

112
src-elm/tests/Fixtures.elm Normal file
View file

@ -0,0 +1,112 @@
module Fixtures exposing (allDirs, dir1, dir2, dir3, dir4, dir5, filteredDir1, filteredDir2, filteredDir3, filteredDir4, filteredDir5, filteredDir6, filteredDir7, filteredDir8, model, windowsDir)
import File exposing (File, FileStatus(..), withName)
import Main exposing (Model, defaultModel)
import Time exposing (millisToPosix)
allDirs : List File
allDirs =
[ dir1
, dir2
, dir3
, dir4
, dir5
]
dir1 : File
dir1 =
{ isDir = True
, mode = 777
, modTime = millisToPosix 0
, name = "dirname"
, parentPath = "/some/path/"
, satisfiesFilter = False
, size = 0
, status = Unselected
}
dir2 : File
dir2 =
{ dir1 | name = "dir2" }
dir3 : File
dir3 =
{ dir1 | name = "different" }
dir4 : File
dir4 =
{ dir1
| name = "dirname4"
, parentPath = "/some/path/extended"
}
dir5 : File
dir5 =
{ dir1
| name = "dir5"
, parentPath = "/some/path/extended"
}
filteredDir1 : File
filteredDir1 =
dir1 |> withSatisfiedFilter
filteredDir2 : File
filteredDir2 =
dir2 |> withSatisfiedFilter
filteredDir3 : File
filteredDir3 =
dir3 |> withSatisfiedFilter
filteredDir4 : File
filteredDir4 =
dir4 |> withSatisfiedFilter
filteredDir5 : File
filteredDir5 =
dir5 |> withSatisfiedFilter
filteredDir6 : File
filteredDir6 =
filteredDir1 |> withName "a name with random chars 1 [123]"
filteredDir7 : File
filteredDir7 =
filteredDir1 |> withName "a name with random chars 2 [456]"
filteredDir8 : File
filteredDir8 =
filteredDir1 |> withName "a name with random chars 3 [678] - Foo"
model : Model
model =
{ defaultModel | destinationSubdirectories = [] }
windowsDir : File
windowsDir =
{ dir1
| name = "windows dir"
, parentPath = "C:\\some\\path\\extended"
}
withSatisfiedFilter : File -> File
withSatisfiedFilter file =
{ file | satisfiesFilter = True }

291
src-elm/tests/MaintTest.elm Normal file
View file

@ -0,0 +1,291 @@
module MaintTest exposing (suite)
import Expect
import File exposing (File, FileStatus(..), defaultDir, withName, withParentPath, withStatus)
import Fixtures exposing (allDirs, dir1, dir2, dir3, dir4, dir5, filteredDir1, filteredDir2, filteredDir3, filteredDir4, filteredDir5, model, windowsDir)
import Main exposing (Model, defaultModel, filterDestinationDirectories, pathElements, select, truncateConcatenatedNames, windowsPathSep)
import Test exposing (Test, describe, test)
suite : Test
suite =
describe "Main module"
[ describe "filterDestinationDirectories"
[ test "identifies filenames containing a given string" <|
\_ ->
let
expected : List File
expected =
[ { dir1 | satisfiesFilter = True }
, dir2
, dir3
, { dir4 | satisfiesFilter = True }
, dir5
]
filteredModel : Model
filteredModel =
{ model
| destinationDirectoryFilter = "dirn"
, destinationSubdirectories = allDirs
}
|> filterDestinationDirectories
in
Expect.equal expected filteredModel.destinationSubdirectories
, test "identifies parent path containing a given string" <|
\_ ->
let
expected : List File
expected =
[ dir1
, dir2
, dir3
, { dir4 | satisfiesFilter = True }
, { dir5 | satisfiesFilter = True }
]
filteredModel : Model
filteredModel =
{ model
| destinationDirectoryFilter = "ext"
, destinationSubdirectories = allDirs
}
|> filterDestinationDirectories
in
Expect.equal expected filteredModel.destinationSubdirectories
, describe "pathElements"
[ test "pathElements returns the list of nested path and their names" <|
\_ ->
let
elements : List File
elements =
pathElements defaultModel [] <| dir5.parentPath ++ defaultModel.pathSeparator ++ dir5.name
expected : List File
expected =
[ defaultDir
|> withName "some"
|> withParentPath "/"
, defaultDir
|> withName "path"
|> withParentPath "/some"
, defaultDir
|> withName "extended"
|> withParentPath "/some/path"
, defaultDir
|> withName "dir5"
|> withParentPath "/some/path/extended"
]
in
Expect.equal expected elements
, test "pathElements returns the list of nested path and their names under Windows" <|
\_ ->
let
elements : List File
elements =
pathElements windowsModel [] <|
windowsDir.parentPath
++ windowsModel.pathSeparator
++ windowsDir.name
expected : List File
expected =
[ defaultDir
|> withName "some"
|> withParentPath "C:"
, defaultDir
|> withName "path"
|> withParentPath "C:\\some"
, defaultDir
|> withName "extended"
|> withParentPath "C:\\some\\path"
, defaultDir
|> withName "windows dir"
|> withParentPath "C:\\some\\path\\extended"
]
windowsModel : Model
windowsModel =
{ defaultModel | pathSeparator = windowsPathSep }
in
Expect.equal expected elements
, test "pathElements ignores ." <|
\_ ->
let
elements : List File
elements =
pathElements defaultModel [] "."
expected : List File
expected =
[]
in
Expect.equal expected elements
]
, test "truncate returns a list of files whose cumulated name length does not exceed given size" <|
\_ ->
let
actual : List File
actual =
truncateConcatenatedNames 22 allDirs
expected : List File
expected =
[ dir3
, dir4
, dir5
]
in
Expect.equal expected actual
]
, describe "select"
[ test "select selects only the clicked file when neither CTRL or SHIFT are pressed" <|
\_ ->
let
actual : List File
actual =
select model allDirs dir3
expected : List File
expected =
[ dir1
, dir2
, dir3 |> withStatus Selected
, dir4
, dir5
]
in
Expect.equal expected actual
, test "select unselects the clicked file when neither CTRL or SHIFT are pressed" <|
\_ ->
let
actual : List File
actual =
select model
[ dir1
, dir2
, clickedFile
, dir4
, dir5
]
dir3
clickedFile : File
clickedFile =
dir3 |> withStatus Selected
expected : List File
expected =
allDirs
in
Expect.equal expected actual
, test "select adds the clicked file to the current selection if it is unselected and CTRL is pressed" <|
\_ ->
let
actual : List File
actual =
select
{ model | isControlPressed = True }
[ dir1
, dir2
, clickedFile
, dir4
, dir5
]
dir4
clickedFile : File
clickedFile =
dir3 |> withStatus Selected
expected : List File
expected =
[ dir1
, dir2
, dir3 |> withStatus Selected
, dir4 |> withStatus Selected
, dir5
]
in
Expect.equal expected actual
, test "select removes the clicked file from the current selection if it is selected and CTRL is pressed" <|
\_ ->
let
actual : List File
actual =
select
{ model | isControlPressed = True }
[ dir1
, dir2
, dir3 |> withStatus Selected
, clickedFile
, dir5
]
clickedFile
clickedFile : File
clickedFile =
dir4 |> withStatus Selected
expected : List File
expected =
[ dir1
, dir2
, dir3 |> withStatus Selected
, dir4
, dir5
]
in
Expect.equal expected actual
, test "select selects the file range from the first selected to the clicked file when it is after and SHIFT is pressed" <|
\_ ->
let
actual : List File
actual =
select
{ model | isShiftPressed = True }
[ filteredDir1
, filteredDir2 |> withStatus Selected
, filteredDir3
, filteredDir4
, filteredDir5
]
filteredDir4
expected : List File
expected =
[ filteredDir1
, filteredDir2 |> withStatus Selected
, filteredDir3 |> withStatus Selected
, filteredDir4 |> withStatus Selected
, filteredDir5
]
in
Expect.equal expected actual
, test "select selects the file range from the clicked file to the last selected when it is before and SHIFT is pressed" <|
\_ ->
let
actual : List File
actual =
select
{ model | isShiftPressed = True }
[ filteredDir1
, filteredDir2
, filteredDir3
, filteredDir4 |> withStatus Selected
, filteredDir5
]
filteredDir2
expected : List File
expected =
[ filteredDir1
, filteredDir2 |> withStatus Selected
, filteredDir3 |> withStatus Selected
, filteredDir4 |> withStatus Selected
, filteredDir5
]
in
Expect.equal expected actual
]
]

View file

@ -0,0 +1,74 @@
module SearchReplaceTest exposing (suite)
import Expect
import Pattern exposing (Token(..), fromString)
import Test exposing (Test, describe, test)
suite : Test
suite =
describe "SearchReplace"
[ describe "fromString"
[ test "parses a string with a joker" <|
\_ ->
let
actual : List Token
actual =
fromString "a*b"
expected : List Token
expected =
[ RawString "a", Joker, RawString "b" ]
in
Expect.equal expected actual
, test "parses a string without any joker" <|
\_ ->
let
actual : List Token
actual =
fromString "ab"
expected : List Token
expected =
[ RawString "ab" ]
in
Expect.equal expected actual
, test "parses a string with several jokers" <|
\_ ->
let
actual : List Token
actual =
fromString "ab*cd*"
expected : List Token
expected =
[ RawString "ab", Joker, RawString "cd", Joker ]
in
Expect.equal expected actual
, test "parses a string with a leading joker" <|
\_ ->
let
actual : List Token
actual =
fromString "*abcd"
expected : List Token
expected =
[ Joker, RawString "abcd" ]
in
Expect.equal expected actual
, test "escapes special chars" <|
\_ ->
let
actual : List Token
actual =
fromString ".[]()"
expected : List Token
expected =
--[ RawString "\\.\\[\\]\\(\\)" ]
[ RawString "\\.\\[\\]\\(\\)" ]
in
Expect.equal expected actual
]
]

View file

@ -0,0 +1,23 @@
module StringComparisonTest exposing (..)
import Expect exposing (Expectation)
import StringComparison exposing (isSimilarityLevelGreaterThan)
import Test exposing (Test, describe, test)
suite : Test
suite =
describe "isSimilarityLevelGreaterThan"
[ test "returns true when comparing string with more than level common chars" <|
\_ ->
isSimilarityLevelGreaterThan "abcdeijk" "abcdefgh" 5
|> Expect.equal True
, test "detects similarity even if the first string is shorter than the second" <|
\_ ->
isSimilarityLevelGreaterThan "zbcde" "abcdefgh" 4
|> Expect.equal True
, test "detects similarity even if the first string is longer than the second" <|
\_ ->
isSimilarityLevelGreaterThan "zBCDExkHamsld" "aBCDEfgH" 5
|> Expect.equal True
]