Integrate elm App with Tauri. Can navigate into source directory.
This commit is contained in:
commit
94003989ef
50 changed files with 41456 additions and 0 deletions
42
src-elm/elm.json
Normal file
42
src-elm/elm.json
Normal 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
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
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
8
src-elm/package.json
Normal 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
49
src-elm/review/elm.json
Normal 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": {}
|
||||
}
|
||||
}
|
||||
77
src-elm/review/src/ReviewConfig.elm
Normal file
77
src-elm/review/src/ReviewConfig.elm
Normal 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
254
src-elm/src/File.elm
Normal 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
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
48
src-elm/src/Pattern.elm
Normal 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 ")" "\\)"
|
||||
53
src-elm/src/StringComparison.elm
Normal file
53
src-elm/src/StringComparison.elm
Normal 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
250
src-elm/tests/FileTest.elm
Normal 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
112
src-elm/tests/Fixtures.elm
Normal 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
291
src-elm/tests/MaintTest.elm
Normal 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
|
||||
]
|
||||
]
|
||||
74
src-elm/tests/SearchReplaceTest.elm
Normal file
74
src-elm/tests/SearchReplaceTest.elm
Normal 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
|
||||
]
|
||||
]
|
||||
23
src-elm/tests/StringComparisonTest.elm
Normal file
23
src-elm/tests/StringComparisonTest.elm
Normal 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
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue