espial-0.0.41: test/Model/NotesSortSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Model.NotesSortSpec (spec) where
import Data.Time (addUTCTime)
import Model.Custom (hashPasswordBCryptWithPolicy, mkNtSlug)
import TestImport
import Types (DB)
t0 :: UTCTime
t0 = UTCTime (fromGregorian 2024 1 1) 0
createTestUser :: DB (Key User)
createTestUser = do
pwHash <- liftIO $ hashPasswordBCryptWithPolicy bcryptTestPolicy "pass"
insert $ User "testuser" pwHash Nothing False False True True False False True Nothing
createNoteAt :: Key User -> Text -> UTCTime -> DB (Key Note)
createNoteAt uid title t = do
slug <- liftIO mkNtSlug
insert $ Note uid slug (length title) title "text" False False t t
queryOrder :: Key User -> NoteSort -> DB [Key Note]
queryOrder uid nsort = do
(_, rows, _, _) <- getNoteList uid Nothing SharedAll (mkNotePaging nsort Nothing 1) 100
return $ map entityKey rows
queryPageFlags :: Key User -> NoteSort -> Limit -> Page -> DB (Bool, Bool)
queryPageFlags uid nsort lim pg = do
(_, _, hasEarlier, hasLater) <- getNoteList uid Nothing SharedAll (mkNotePaging nsort Nothing pg) lim
return (hasEarlier, hasLater)
spec :: Spec
spec = withApp $ do
describe "getNoteList sort" $ do
-- ─── NoteSortCreated ─────────────────────────────────────────────────
describe "NoteSortCreated" $ do
it "SortAsc orders oldest first" $ do
(uid, nOld, nNew) <- runDB $ do
uid <- createTestUser
nNew <- createNoteAt uid "new" (addUTCTime 100 t0)
nOld <- createNoteAt uid "old" t0
return (uid, nOld, nNew)
nids <- runDB $ queryOrder uid (NoteSort NoteSortCreated SortAsc)
liftIO $ nids `shouldBe` [nOld, nNew]
it "SortDesc orders newest first" $ do
(uid, nOld, nNew) <- runDB $ do
uid <- createTestUser
nNew <- createNoteAt uid "new" (addUTCTime 100 t0)
nOld <- createNoteAt uid "old" t0
return (uid, nOld, nNew)
nids <- runDB $ queryOrder uid (NoteSort NoteSortCreated SortDesc)
liftIO $ nids `shouldBe` [nNew, nOld]
-- ─── NoteSortTitle ───────────────────────────────────────────────────
describe "NoteSortTitle" $ do
it "SortAsc orders case-insensitively" $ do
-- byte/ASCII ordering would put "Zebra" ('Z'=90) before "apple"
-- ('a'=97); a correct case-insensitive sort must not do that
(uid, nApple, nZebra) <- runDB $ do
uid <- createTestUser
nZebra <- createNoteAt uid "Zebra" t0
nApple <- createNoteAt uid "apple" t0
return (uid, nApple, nZebra)
nids <- runDB $ queryOrder uid (NoteSort NoteSortTitle SortAsc)
liftIO $ nids `shouldBe` [nApple, nZebra]
it "SortDesc reverses the order" $ do
(uid, nApple, nZebra) <- runDB $ do
uid <- createTestUser
nZebra <- createNoteAt uid "Zebra" t0
nApple <- createNoteAt uid "apple" t0
return (uid, nApple, nZebra)
nids <- runDB $ queryOrder uid (NoteSort NoteSortTitle SortDesc)
liftIO $ nids `shouldBe` [nZebra, nApple]
-- ─── cursor paging is only defined for created sorts ───────────────────
describe "before/after cursor paging" $ do
it "has no filtering effect when sorting by a non-default field" $ do
(uid, nOld, nNew) <- runDB $ do
uid <- createTestUser
nOld <- createNoteAt uid "aaa" t0
nNew <- createNoteAt uid "bbb" (addUTCTime 100 t0)
return (uid, nOld, nNew)
-- a "before t0" cursor would normally exclude nOld under created
-- sort; under title sort mkNotePaging must discard it entirely
(_, rows, _, _) <-
runDB
$ getNoteList
uid
Nothing
SharedAll
(mkNotePaging (NoteSort NoteSortTitle SortAsc) (Just (PagingCursorBefore (NoteCursor t0 Nothing))) 1)
100
let nids = map entityKey rows
liftIO $ nids `shouldBe` [nOld, nNew]
it "does not reverse results when an after-cursor is given with a non-default sort" $ do
-- an "after t0" cursor would normally exclude nOld and, on the
-- PagingCursorAfter path, trigger a reverse() to restore display
-- order; under title sort neither must happen
(uid, nOld, nNew) <- runDB $ do
uid <- createTestUser
nOld <- createNoteAt uid "aaa" t0
nNew <- createNoteAt uid "bbb" (addUTCTime 100 t0)
return (uid, nOld, nNew)
(_, rows, _, _) <-
runDB
$ getNoteList
uid
Nothing
SharedAll
(mkNotePaging (NoteSort NoteSortTitle SortAsc) (Just (PagingCursorAfter (NoteCursor t0 Nothing))) 1)
100
let nids = map entityKey rows
liftIO $ nids `shouldBe` [nOld, nNew]
-- ─── hasEarlier/hasLater fallback (page/offset) paging ─────────────────
describe "hasEarlier/hasLater for non-default sorts (page/offset fallback)" $ do
it "first page with more rows beyond the limit: hasEarlier=False, hasLater=True" $ do
uid <- runDB $ do
uid <- createTestUser
mapM_ (\n -> void $ createNoteAt uid (tshow (n :: Int)) t0) [1 .. 3]
return uid
(hasEarlier, hasLater) <- runDB $ queryPageFlags uid (NoteSort NoteSortTitle SortAsc) 2 1
liftIO $ hasEarlier `shouldBe` False
liftIO $ hasLater `shouldBe` True
it "last page: hasEarlier=True, hasLater=False" $ do
uid <- runDB $ do
uid <- createTestUser
mapM_ (\n -> void $ createNoteAt uid (tshow (n :: Int)) t0) [1 .. 3]
return uid
(hasEarlier, hasLater) <- runDB $ queryPageFlags uid (NoteSort NoteSortTitle SortAsc) 2 2
liftIO $ hasEarlier `shouldBe` True
liftIO $ hasLater `shouldBe` False
it "single page, all rows fit: hasEarlier=False, hasLater=False" $ do
uid <- runDB $ do
uid <- createTestUser
_ <- createNoteAt uid "a" t0
return uid
(hasEarlier, hasLater) <- runDB $ queryPageFlags uid (NoteSort NoteSortTitle SortAsc) 100 1
liftIO $ hasEarlier `shouldBe` False
liftIO $ hasLater `shouldBe` False