Add Timeline framework
The tests fail, but using test-driven development we will now build them functionalpull/17/head
parent
e8ee125def
commit
211f8f1df4
|
@ -0,0 +1,114 @@
|
||||||
|
module Internal.Values.Timeline exposing
|
||||||
|
( Batch, fromToken, fromSlice
|
||||||
|
, Timeline
|
||||||
|
, empty, singleton
|
||||||
|
, mostRecentEvents
|
||||||
|
, addSync, insert
|
||||||
|
)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
|
||||||
|
|
||||||
|
# Timeline
|
||||||
|
|
||||||
|
The Timeline data type represents a timeline in the Matrix room. The Matrix room
|
||||||
|
timeline is quite a complex data type, as it is constantly only partially known
|
||||||
|
by the Matrix client. This module exposes a data type that helps explore, track
|
||||||
|
and maintain this room state.
|
||||||
|
|
||||||
|
|
||||||
|
## Batch
|
||||||
|
|
||||||
|
@docs Batch, fromToken, fromSlice
|
||||||
|
|
||||||
|
|
||||||
|
## Timeline
|
||||||
|
|
||||||
|
@docs Timeline
|
||||||
|
|
||||||
|
|
||||||
|
## Create
|
||||||
|
|
||||||
|
@docs empty, singleton
|
||||||
|
|
||||||
|
|
||||||
|
## Query
|
||||||
|
|
||||||
|
@docs mostRecentEvents
|
||||||
|
|
||||||
|
|
||||||
|
## Manipulate
|
||||||
|
|
||||||
|
@docs addSync, insert
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Internal.Filter.Timeline as Filter exposing (Filter)
|
||||||
|
|
||||||
|
|
||||||
|
{-| The Timeline type represents the timeline state in a Matrix room.
|
||||||
|
-}
|
||||||
|
type Timeline
|
||||||
|
= Timeline
|
||||||
|
|
||||||
|
|
||||||
|
{-| A batch is a batch of events that is placed onto the Timeline. Functions
|
||||||
|
that require an insertion, generally require this data type.
|
||||||
|
-}
|
||||||
|
type Batch
|
||||||
|
= Batch
|
||||||
|
|
||||||
|
|
||||||
|
{-| When syncing a Matrix room to its most recent state, add the most recent
|
||||||
|
batch to the front of the Timeline.
|
||||||
|
-}
|
||||||
|
addSync : Batch -> Timeline -> Timeline
|
||||||
|
addSync _ timeline =
|
||||||
|
timeline
|
||||||
|
|
||||||
|
|
||||||
|
{-| Create a new empty timeline.
|
||||||
|
-}
|
||||||
|
empty : Timeline
|
||||||
|
empty =
|
||||||
|
Timeline
|
||||||
|
|
||||||
|
|
||||||
|
{-| Turn a single token into a batch.
|
||||||
|
-}
|
||||||
|
fromToken : String -> Batch
|
||||||
|
fromToken _ =
|
||||||
|
Batch
|
||||||
|
|
||||||
|
|
||||||
|
{-| Turn a slice of events into a batch.
|
||||||
|
|
||||||
|
NOTE: `start` must generally be a value. If it is `Nothing`, then it is
|
||||||
|
connected until the start of the timeline.
|
||||||
|
|
||||||
|
-}
|
||||||
|
fromSlice : { start : Maybe String, events : List String, filter : Filter, end : String } -> Batch
|
||||||
|
fromSlice _ =
|
||||||
|
Batch
|
||||||
|
|
||||||
|
|
||||||
|
{-| Insert a batch anywhere else in the timeline.
|
||||||
|
-}
|
||||||
|
insert : Batch -> Timeline -> Timeline
|
||||||
|
insert _ timeline =
|
||||||
|
timeline
|
||||||
|
|
||||||
|
|
||||||
|
{-| Under a given filter, find the most recent events.
|
||||||
|
-}
|
||||||
|
mostRecentEvents : Filter -> Timeline -> List String
|
||||||
|
mostRecentEvents _ _ =
|
||||||
|
[]
|
||||||
|
|
||||||
|
|
||||||
|
{-| Create a timeline with a single batch inserted. This batch is considered the
|
||||||
|
most recent batch, as if created by a sync.
|
||||||
|
-}
|
||||||
|
singleton : Batch -> Timeline
|
||||||
|
singleton b =
|
||||||
|
addSync b empty
|
|
@ -63,6 +63,14 @@ suite =
|
||||||
(Filter.onlyTypes (head :: tail))
|
(Filter.onlyTypes (head :: tail))
|
||||||
(Filter.onlySenders (head :: tail))
|
(Filter.onlySenders (head :: tail))
|
||||||
)
|
)
|
||||||
|
, fuzz2 fuzzer
|
||||||
|
fuzzer
|
||||||
|
"Filter.and f1 f2 == pass iff f1 == f2 == pass"
|
||||||
|
(\filter1 filter2 ->
|
||||||
|
Expect.equal
|
||||||
|
(Filter.and filter1 filter2 == Filter.pass)
|
||||||
|
(filter1 == Filter.pass && filter2 == Filter.pass)
|
||||||
|
)
|
||||||
]
|
]
|
||||||
, describe "Event filters"
|
, describe "Event filters"
|
||||||
[ fuzz TestEvent.fuzzer
|
[ fuzz TestEvent.fuzzer
|
||||||
|
|
|
@ -0,0 +1,303 @@
|
||||||
|
module Test.Values.Timeline exposing (..)
|
||||||
|
|
||||||
|
import Fuzz exposing (Fuzzer)
|
||||||
|
import Internal.Filter.Timeline as Filter exposing (Filter)
|
||||||
|
import Internal.Values.Timeline as Timeline exposing (Batch, Timeline)
|
||||||
|
import Test exposing (..)
|
||||||
|
import Test.Filter.Timeline as TestFilter
|
||||||
|
import Expect
|
||||||
|
|
||||||
|
|
||||||
|
fuzzer : Fuzzer Timeline
|
||||||
|
fuzzer =
|
||||||
|
Fuzz.map2
|
||||||
|
(\makers filter ->
|
||||||
|
case makers of
|
||||||
|
[] ->
|
||||||
|
Timeline.empty
|
||||||
|
|
||||||
|
head :: tail ->
|
||||||
|
List.foldl
|
||||||
|
(\maker ( prevToken, timeline ) ->
|
||||||
|
case maker of
|
||||||
|
Sync start events end ->
|
||||||
|
( end
|
||||||
|
, Timeline.addSync
|
||||||
|
(Timeline.fromSlice
|
||||||
|
{ start =
|
||||||
|
start
|
||||||
|
|> Maybe.withDefault prevToken
|
||||||
|
|> Maybe.Just
|
||||||
|
, events = events
|
||||||
|
, filter = filter
|
||||||
|
, end = end
|
||||||
|
}
|
||||||
|
)
|
||||||
|
timeline
|
||||||
|
)
|
||||||
|
|
||||||
|
Get start events efilter end ->
|
||||||
|
( prevToken
|
||||||
|
, Timeline.insert
|
||||||
|
(Timeline.fromSlice
|
||||||
|
{ start = start
|
||||||
|
, events = events
|
||||||
|
, filter = Filter.and filter efilter
|
||||||
|
, end = end
|
||||||
|
}
|
||||||
|
)
|
||||||
|
timeline
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(case head of
|
||||||
|
Sync start events end ->
|
||||||
|
( end
|
||||||
|
, Timeline.addSync
|
||||||
|
(Timeline.fromSlice
|
||||||
|
{ start = start
|
||||||
|
, events = events
|
||||||
|
, filter = filter
|
||||||
|
, end = end
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Timeline.empty
|
||||||
|
)
|
||||||
|
|
||||||
|
Get start events efilter end ->
|
||||||
|
( end
|
||||||
|
, Timeline.addSync
|
||||||
|
(Timeline.fromSlice
|
||||||
|
{ start = start
|
||||||
|
, events = events
|
||||||
|
, filter = Filter.and filter efilter
|
||||||
|
, end = end
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Timeline.empty
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tail
|
||||||
|
|> Tuple.second
|
||||||
|
)
|
||||||
|
(Fuzz.list fuzzerMaker)
|
||||||
|
TestFilter.fuzzer
|
||||||
|
|
||||||
|
|
||||||
|
fuzzerBatch : Fuzzer Batch
|
||||||
|
fuzzerBatch =
|
||||||
|
Fuzz.oneOf
|
||||||
|
[ Fuzz.map Timeline.fromToken Fuzz.string
|
||||||
|
, Fuzz.map4
|
||||||
|
(\start events filter end ->
|
||||||
|
Timeline.fromSlice
|
||||||
|
{ start = start
|
||||||
|
, events = events
|
||||||
|
, filter = filter
|
||||||
|
, end = end
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Fuzz.maybe Fuzz.string)
|
||||||
|
(Fuzz.list Fuzz.string)
|
||||||
|
TestFilter.fuzzer
|
||||||
|
Fuzz.string
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
type FuzzMaker
|
||||||
|
= Sync (Maybe String) (List String) String
|
||||||
|
| Get (Maybe String) (List String) Filter String
|
||||||
|
|
||||||
|
|
||||||
|
fuzzerMaker : Fuzzer FuzzMaker
|
||||||
|
fuzzerMaker =
|
||||||
|
Fuzz.frequency
|
||||||
|
[ ( 30, Fuzz.map (Sync Nothing []) Fuzz.string )
|
||||||
|
, ( 10
|
||||||
|
, Fuzz.map2 (Sync Nothing)
|
||||||
|
(Fuzz.listOfLengthBetween 1 32 Fuzz.string)
|
||||||
|
Fuzz.string
|
||||||
|
)
|
||||||
|
, ( 1
|
||||||
|
, Fuzz.map3 (\start events end -> Sync (Just start) events end)
|
||||||
|
Fuzz.string
|
||||||
|
(Fuzz.listOfLengthBetween 1 32 Fuzz.string)
|
||||||
|
Fuzz.string
|
||||||
|
)
|
||||||
|
, ( 1
|
||||||
|
, Fuzz.map4 Get
|
||||||
|
(Fuzz.maybe Fuzz.string)
|
||||||
|
(Fuzz.list Fuzz.string)
|
||||||
|
TestFilter.fuzzer
|
||||||
|
Fuzz.string
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
fuzzerForBatch : Fuzzer { start : String, events : List String, filter : Filter, end : String }
|
||||||
|
fuzzerForBatch =
|
||||||
|
Fuzz.map4
|
||||||
|
(\start events filter end ->
|
||||||
|
{ start = start, events = events, filter = filter, end = end }
|
||||||
|
)
|
||||||
|
Fuzz.string
|
||||||
|
(Fuzz.list Fuzz.string)
|
||||||
|
TestFilter.fuzzer
|
||||||
|
Fuzz.string
|
||||||
|
|
||||||
|
|
||||||
|
suite : Test
|
||||||
|
suite =
|
||||||
|
describe "Timeline"
|
||||||
|
[ describe "Most recent events"
|
||||||
|
[ fuzz fuzzerForBatch "Singleton is most recent"
|
||||||
|
(\batch ->
|
||||||
|
{ start = Just batch.start
|
||||||
|
, events = batch.events
|
||||||
|
, filter = batch.filter
|
||||||
|
, end = batch.end
|
||||||
|
}
|
||||||
|
|> Timeline.fromSlice
|
||||||
|
|> Timeline.singleton
|
||||||
|
|> Timeline.mostRecentEvents batch.filter
|
||||||
|
|> Expect.equal batch.events
|
||||||
|
)
|
||||||
|
, fuzz2 fuzzerForBatch fuzzerForBatch "Double batch connects"
|
||||||
|
(\batch1 batch2 ->
|
||||||
|
[ { start = Just batch1.start
|
||||||
|
, events = batch1.events
|
||||||
|
, filter = batch1.filter
|
||||||
|
, end = batch2.start
|
||||||
|
}
|
||||||
|
, { start = Just batch2.start
|
||||||
|
, events = batch2.events
|
||||||
|
, filter = batch2.filter
|
||||||
|
, end = batch2.end
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|> List.map Timeline.fromSlice
|
||||||
|
|> List.foldl Timeline.addSync Timeline.empty
|
||||||
|
|> Timeline.mostRecentEvents (Filter.and batch1.filter batch2.filter)
|
||||||
|
|> (\outcome ->
|
||||||
|
if batch2.start == batch2.end then
|
||||||
|
Expect.equal [] outcome
|
||||||
|
else if batch1.start == batch2.start then
|
||||||
|
Expect.equal batch2.events outcome
|
||||||
|
else
|
||||||
|
Expect.equal
|
||||||
|
(List.append batch1.events batch2.events)
|
||||||
|
outcome
|
||||||
|
)
|
||||||
|
)
|
||||||
|
, fuzz2 fuzzerForBatch fuzzerForBatch "Disconnected double batch does not connect"
|
||||||
|
(\batch1 batch2 ->
|
||||||
|
[ { start = Just batch1.start
|
||||||
|
, events = batch1.events
|
||||||
|
, filter = batch1.filter
|
||||||
|
, end = batch1.start
|
||||||
|
}
|
||||||
|
, { start = Just batch2.start
|
||||||
|
, events = batch2.events
|
||||||
|
, filter = batch2.filter
|
||||||
|
, end = batch2.end
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|> List.map Timeline.fromSlice
|
||||||
|
|> List.foldl Timeline.addSync Timeline.empty
|
||||||
|
|> Timeline.mostRecentEvents (Filter.and batch1.filter batch2.filter)
|
||||||
|
|> (\outcome ->
|
||||||
|
if batch2.start == batch2.end then
|
||||||
|
Expect.equal [] outcome
|
||||||
|
else if batch1.start == batch2.start then
|
||||||
|
Expect.equal batch2.events outcome
|
||||||
|
else if batch1.end == batch2.start then
|
||||||
|
Expect.equal
|
||||||
|
(List.append batch1.events batch2.events)
|
||||||
|
outcome
|
||||||
|
else
|
||||||
|
Expect.equal batch2.events outcome
|
||||||
|
)
|
||||||
|
)
|
||||||
|
, fuzz
|
||||||
|
( Fuzz.pair Fuzz.int (Fuzz.list Fuzz.string)
|
||||||
|
|> (\f -> Fuzz.triple f f f)
|
||||||
|
|> (\f -> Fuzz.triple f f f)
|
||||||
|
)
|
||||||
|
"Connect 8 batches"
|
||||||
|
(\(((i1, e1), (i2, e2), (i3, e3)), ((i4, e4), (i5, e5), (i6, e6)), ((i7, e7), (i8, e8), (_, e9))) ->
|
||||||
|
[ ( i1
|
||||||
|
, { start = Just <| String.fromInt 1
|
||||||
|
, events = e1
|
||||||
|
, filter = Filter.pass
|
||||||
|
, end = String.fromInt (1 + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
, ( i2
|
||||||
|
, { start = Just <| String.fromInt 2
|
||||||
|
, events = e2
|
||||||
|
, filter = Filter.pass
|
||||||
|
, end = String.fromInt (2 + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
, ( i3
|
||||||
|
, { start = Just <| String.fromInt 3
|
||||||
|
, events = e3
|
||||||
|
, filter = Filter.pass
|
||||||
|
, end = String.fromInt (3 + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
, ( i4
|
||||||
|
, { start = Just <| String.fromInt 4
|
||||||
|
, events = e4
|
||||||
|
, filter = Filter.pass
|
||||||
|
, end = String.fromInt (4 + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
, ( i5
|
||||||
|
, { start = Just <| String.fromInt 5
|
||||||
|
, events = e5
|
||||||
|
, filter = Filter.pass
|
||||||
|
, end = String.fromInt (5 + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
, ( i6
|
||||||
|
, { start = Just <| String.fromInt 6
|
||||||
|
, events = e6
|
||||||
|
, filter = Filter.pass
|
||||||
|
, end = String.fromInt (6 + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
, ( i7
|
||||||
|
, { start = Just <| String.fromInt 7
|
||||||
|
, events = e7
|
||||||
|
, filter = Filter.pass
|
||||||
|
, end = String.fromInt (7 + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
, ( i8
|
||||||
|
, { start = Just <| String.fromInt 8
|
||||||
|
, events = e8
|
||||||
|
, filter = Filter.pass
|
||||||
|
, end = String.fromInt (8 + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|> List.sortBy Tuple.first
|
||||||
|
|> List.map Tuple.second
|
||||||
|
|> List.map Timeline.fromSlice
|
||||||
|
|> List.foldl
|
||||||
|
Timeline.insert
|
||||||
|
(Timeline.singleton
|
||||||
|
( Timeline.fromSlice
|
||||||
|
{ start = Just <| String.fromInt 9
|
||||||
|
, events = e9
|
||||||
|
, filter = Filter.pass
|
||||||
|
, end = String.fromInt (9 + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> Timeline.mostRecentEvents Filter.pass
|
||||||
|
|> Expect.equal
|
||||||
|
( e1 ++ e2 ++ e3 ++ e4 ++ e5 ++ e6 ++ e7 ++ e8 ++ e9 )
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
Loading…
Reference in New Issue