From 978517d25471ccc91d0c774f86c8b2bd8b420c8e Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 13:21:17 +0100 Subject: [PATCH 01/23] Adopt native code conventions of Elm 0.17 --- src/Native/ElmFire.js | 113 ++++++++++++++++--------------------- src/Native/ElmFire/Auth.js | 72 +++++++++-------------- 2 files changed, 78 insertions(+), 107 deletions(-) diff --git a/src/Native/ElmFire.js b/src/Native/ElmFire.js index 7168d84..5752477 100644 --- a/src/Native/ElmFire.js +++ b/src/Native/ElmFire.js @@ -1,19 +1,7 @@ /* @flow */ /* global Elm, Firebase, F2, F3, F4 */ -Elm.Native.ElmFire = {}; -Elm.Native.ElmFire.make = function (localRuntime) { - "use strict"; - - localRuntime.Native = localRuntime.Native || {}; - localRuntime.Native.ElmFire = localRuntime.Native.ElmFire || {}; - if (localRuntime.Native.ElmFire.values) { - return localRuntime.Native.ElmFire.values; - } - - var Utils = Elm.Native.Utils.make (localRuntime); - var Task = Elm.Native.Task.make (localRuntime); - var List = Elm.Native.List.make (localRuntime); +var _ThomasWeiser$elmfire$Native_ElmFire = function () { var pleaseReportThis = ' Should not happen, please report this as a bug in ElmFire!'; @@ -66,15 +54,15 @@ Elm.Native.ElmFire.make = function (localRuntime) { } function fbTaskFail (fbError) { - return Task.fail (fbTaskError (fbError)); + return _elm_lang$core$Native_Scheduler.fail (fbTaskError (fbError)); } - function exTaskError (exception) { + function exceptionTaskError (exception) { return error2elm ('OtherFirebaseError', exception.toString ()); } - function exTaskFail (exception) { - return Task.fail (exTaskError (exception)); + function exceptionTaskFail (exception) { + return _elm_lang$core$Native_Scheduler.fail (exceptionTaskError (exception)); } function onCompleteCallbackRef (callback, res) { @@ -82,7 +70,7 @@ Elm.Native.ElmFire.make = function (localRuntime) { if (err) { callback (fbTaskFail (err)); } else { - callback (Task.succeed (res)); + callback (_elm_lang$core$Native_Scheduler.succeed (res)); } }; } @@ -122,7 +110,7 @@ Elm.Native.ElmFire.make = function (localRuntime) { ref = getRefStep (location); } catch (exception) { - failureCallback (Task.fail (error2elm ('LocationError', exception.toString ()))); + failureCallback (_elm_lang$core$Native_Scheduler.fail (error2elm ('LocationError', exception.toString ()))); } return ref; } @@ -140,107 +128,107 @@ Elm.Native.ElmFire.make = function (localRuntime) { } function open (location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { - callback (Task.succeed (ref)); + callback (_elm_lang$core$Native_Scheduler.succeed (ref)); } }); } function set (onDisconnect, value, location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var onComplete; if (onDisconnect) { ref = ref.onDisconnect (); - onComplete = onCompleteCallbackRef (callback, Utils.Tuple0); + onComplete = onCompleteCallbackRef (callback, _elm_lang$core$Native_Utils.Tuple0); } else { onComplete = onCompleteCallbackRef (callback, ref) } try { ref.set (value, onComplete); } - catch (exception) { callback (exTaskFail (exception)); } + catch (exception) { callback (exceptionTaskFail (exception)); } } }); } function setWithPriority (onDisconnect, value, priority, location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var onComplete; if (onDisconnect) { ref = ref.onDisconnect (); - onComplete = onCompleteCallbackRef (callback, Utils.Tuple0); + onComplete = onCompleteCallbackRef (callback, _elm_lang$core$Native_Utils.Tuple0); } else { onComplete = onCompleteCallbackRef (callback, ref) } try { ref.setWithPriority (value, priority2fb (priority), onComplete); } - catch (exception) { callback (exTaskFail (exception)); } + catch (exception) { callback (exceptionTaskFail (exception)); } } }); } function setPriority (priority, location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { try { ref.setPriority (priority2fb (priority), onCompleteCallbackRef (callback, ref)); } - catch (exception) { callback (exTaskFail (exception)); } + catch (exception) { callback (exceptionTaskFail (exception)); } } }); } function update (onDisconnect, value, location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var onComplete; if (onDisconnect) { ref = ref.onDisconnect (); - onComplete = onCompleteCallbackRef (callback, Utils.Tuple0); + onComplete = onCompleteCallbackRef (callback, _elm_lang$core$Native_Utils.Tuple0); } else { onComplete = onCompleteCallbackRef (callback, ref) } try { ref.update (value, onComplete); } - catch (exception) { callback (exTaskFail (exception)); } + catch (exception) { callback (exceptionTaskFail (exception)); } } }); } function remove (onDisconnect, location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var onComplete; if (onDisconnect) { ref = ref.onDisconnect (); - onComplete = onCompleteCallbackRef (callback, Utils.Tuple0); + onComplete = onCompleteCallbackRef (callback, _elm_lang$core$Native_Utils.Tuple0); } else { onComplete = onCompleteCallbackRef (callback, ref) } try { ref.remove (onComplete); } - catch (exception) { callback (exTaskFail (exception)); } + catch (exception) { callback (exceptionTaskFail (exception)); } } }); } function onDisconnectCancel (location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { - try { ref.onDisconnect().cancel (onCompleteCallbackRef (callback, Utils.Tuple0)); } - catch (exception) { callback (exTaskFail (exception)); } + try { ref.onDisconnect().cancel (onCompleteCallbackRef (callback, _elm_lang$core$Native_Utils.Tuple0)); } + catch (exception) { callback (exceptionTaskFail (exception)); } } }); } function transaction (updateFunc, location, applyLocally) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var fbUpdateFunc = function (prevVal) { @@ -257,13 +245,13 @@ Elm.Native.ElmFire.make = function (localRuntime) { callback (fbTaskFail (err)); } else { var snapshot = snapshot2elm ('_transaction_', fbSnapshot, null); - var res = Utils.Tuple2 (committed, snapshot); - callback (Task.succeed (res)); + var res = _elm_lang$core$Native_Utils.Tuple2 (committed, snapshot); + callback (_elm_lang$core$Native_Scheduler.succeed (res)); } }; try { ref.transaction (fbUpdateFunc, onComplete, applyLocally); } catch (exception) { - callback (exTaskFail (exception)); + callback (exceptionTaskFail (exception)); } } }); @@ -386,7 +374,7 @@ Elm.Native.ElmFire.make = function (localRuntime) { } function subscribeConditional (createResponseTask, createCancellationTask, query, location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var subscriptionId = nextSubscriptionId (); @@ -394,7 +382,7 @@ Elm.Native.ElmFire.make = function (localRuntime) { var snapshot = snapshot2elm (subscriptionId, fbSnapshot, prevKey); var responseTask = fromMaybe (createResponseTask (snapshot)); if (responseTask !== null) { - Task .perform (responseTask); + _elm_lang$core$Native_Scheduler .rawSpawn (responseTask); } }; var onCancel = function (err) { @@ -403,7 +391,7 @@ Elm.Native.ElmFire.make = function (localRuntime) { _0: subscriptionId, _1: fbTaskError (err) }; - Task .perform (createCancellationTask (cancellation)); + _elm_lang$core$Native_Scheduler .rawSpawn (createCancellationTask (cancellation)); }; var eventType = queryEventType (query); subscriptions [subscriptionId] = { @@ -415,41 +403,41 @@ Elm.Native.ElmFire.make = function (localRuntime) { try { queryOrderAndFilter (query, ref) .on (eventType, onResponse, onCancel); } catch (exception) { - callback (exTaskFail (exception)); + callback (exceptionTaskFail (exception)); return; } - callback (Task.succeed (subscriptionId)); + callback (_elm_lang$core$Native_Scheduler.succeed (subscriptionId)); } }); } function unsubscribe (subscription) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { if (subscriptions.hasOwnProperty (subscription)) { var query = subscriptions [subscription]; delete subscriptions [subscription]; try { query.ref.off (query.eventType, query.callback); } catch (exception) { - callback (exTaskFail (exception)); + callback (exceptionTaskFail (exception)); return; } - Task.perform (query.createCancellationTask ({ + _elm_lang$core$Native_Scheduler.rawSpawn (query.createCancellationTask ({ ctor: 'Unsubscribed', _0: subscription })); - callback (Task.succeed (Utils.Tuple0)); + callback (_elm_lang$core$Native_Scheduler.succeed (_elm_lang$core$Native_Utils.Tuple0)); } else { - callback (Task.fail ({ ctor: 'UnknownSubscription' })); + callback (_elm_lang$core$Native_Scheduler.fail ({ ctor: 'UnknownSubscription' })); } }); } function once (query, location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var onResponse = function (fbSnapshot, prevKey) { var snapshot = snapshot2elm ('_once_', fbSnapshot, prevKey); - callback (Task.succeed (snapshot)); + callback (_elm_lang$core$Native_Scheduler.succeed (snapshot)); }; var onCancel = function (err) { var error = fbTaskFail (err); @@ -459,7 +447,7 @@ Elm.Native.ElmFire.make = function (localRuntime) { try { queryOrderAndFilter (query, ref) .once (eventType, onResponse, onCancel); } catch (exception) { - callback (exTaskFail (exception)); + callback (exceptionTaskFail (exception)); } } }); @@ -473,7 +461,7 @@ Elm.Native.ElmFire.make = function (localRuntime) { prevKey = childSnapshot .key; arr .push (childSnapshot); }); - return List.fromArray (arr); + return _elm_lang$core$List$fromArray (arr); } function toListGeneric (snapshot, mapSnapshot) { @@ -481,7 +469,7 @@ Elm.Native.ElmFire.make = function (localRuntime) { snapshot .intern_ .forEach (function (fbChildSnapshot) { arr .push (mapSnapshot (fbChildSnapshot)); }); - return List.fromArray (arr); + return _elm_lang$core$List$fromArray (arr); } function toValueList (snapshot) { @@ -498,7 +486,7 @@ Elm.Native.ElmFire.make = function (localRuntime) { function toPairList (snapshot) { return toListGeneric (snapshot, function (fbChildSnapshot) { - return Utils.Tuple2 (fbChildSnapshot .key (), fbChildSnapshot .val ()); + return _elm_lang$core$Native_Utils.Tuple2 (fbChildSnapshot .key (), fbChildSnapshot .val ()); }); } @@ -507,20 +495,19 @@ Elm.Native.ElmFire.make = function (localRuntime) { } function setOffline (off) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { if (off) { Firebase.goOffline (); } else { Firebase.goOnline (); } - callback (Task.succeed (Utils.Tuple0)); + callback (_elm_lang$core$Native_Scheduler.succeed (_elm_lang$core$Native_Utils.Tuple0)); }); } var serverTimeStamp = Firebase.ServerValue.TIMESTAMP; - return localRuntime.Native.ElmFire.values = - { + return { // Values exported to Elm toUrl: toUrl , key: key @@ -548,4 +535,4 @@ Elm.Native.ElmFire.make = function (localRuntime) { , getRef: getRef , pleaseReportThis: pleaseReportThis }; -}; +} (); diff --git a/src/Native/ElmFire/Auth.js b/src/Native/ElmFire/Auth.js index fdd8f5b..871519e 100644 --- a/src/Native/ElmFire/Auth.js +++ b/src/Native/ElmFire/Auth.js @@ -1,25 +1,9 @@ /* @flow */ /* global Elm, Firebase, F2, F3, F4 */ -Elm.Native.ElmFire = Elm.Native.ElmFire || {}; -Elm.Native.ElmFire.Auth = {}; -Elm.Native.ElmFire.Auth.make = function (localRuntime) { - "use strict"; +var _ThomasWeiser$elmfire$Native_ElmFire_Auth = function () { - localRuntime.Native = localRuntime.Native || {}; - localRuntime.Native.ElmFire = localRuntime.Native.ElmFire || {}; - localRuntime.Native.ElmFire.Auth = localRuntime.Native.ElmFire.Auth || {}; - if (localRuntime.Native.ElmFire.Auth.values) { - return localRuntime.Native.ElmFire.Auth.values; - } - - var Utils = Elm.Native.Utils.make (localRuntime); - var Task = Elm.Native.Task.make (localRuntime); - - var Date = Elm.Date.make (localRuntime); - var Time = Elm.Time.make (localRuntime); - - var ElmFire = Elm.Native.ElmFire.make (localRuntime); + var ElmFire = _ThomasWeiser$elmfire$Native_ElmFire; var asMaybe = ElmFire.asMaybe; var getRef = ElmFire.getRef; var pleaseReportThis = ElmFire.pleaseReportThis; @@ -72,11 +56,11 @@ Elm.Native.ElmFire.Auth.make = function (localRuntime) { } function fbAuthTaskFail (fbAuthError) { - return Task .fail (fbAuthTaskError (fbAuthError)); + return _elm_lang$core$Native_Scheduler .fail (fbAuthTaskError (fbAuthError)); } - function exAuthTaskFail (exception) { - return Task.fail (authError2elm ('OtherAuthenticationError', exception.toString ())); + function exceptionAuthTaskFail (exception) { + return _elm_lang$core$Native_Scheduler.fail (authError2elm ('OtherAuthenticationError', exception.toString ())); } function auth2elm (fbAuth) { @@ -88,7 +72,7 @@ Elm.Native.ElmFire.Auth.make = function (localRuntime) { uid: fbAuth .uid, provider: fbAuth .provider, token: fbAuth .token, - expires: Date .fromTime (fbAuth .expires * Time .second), + expires: _elm_lang$core$Date$fromTime (fbAuth .expires * _elm_lang$core$Date$second), auth: JSON .parse (JSON .stringify (fbAuth .auth)), specifics: specifics }; @@ -103,57 +87,57 @@ Elm.Native.ElmFire.Auth.make = function (localRuntime) { } function onAuthCallback (fbAuth) { - Task .perform (this.createResponseTask (maybeAuth2elm (fbAuth))); + _elm_lang$core$Native_Scheduler .rawSpawn (this.createResponseTask (maybeAuth2elm (fbAuth))); } function subscribeAuth (createResponseTask, location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var context = { createResponseTask: createResponseTask }; try { ref.onAuth (onAuthCallback, context); } catch (exception) { - callback (exAuthTaskFail (exception)); + callback (exceptionAuthTaskFail (exception)); return; } - callback (Task.succeed (Utils.Tuple0)); + callback (_elm_lang$core$Native_Scheduler.succeed (_elm_lang$core$Native_Utils.Tuple0)); } }); } function unsubscribeAuth (location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { try { ref.offAuth (onAuthCallback); } catch (exception) { - callback (exAuthTaskFail (exception)); + callback (exceptionAuthTaskFail (exception)); return; } - callback (Task.succeed (Utils.Tuple0)); + callback (_elm_lang$core$Native_Scheduler.succeed (_elm_lang$core$Native_Utils.Tuple0)); } }); } function getAuth (location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var auth; try { auth = ref .getAuth (); } catch (exception) { - callback (exAuthTaskFail (exception)); + callback (exceptionAuthTaskFail (exception)); return; } - callback (Task.succeed (maybeAuth2elm (auth))); + callback (_elm_lang$core$Native_Scheduler.succeed (maybeAuth2elm (auth))); } }); } function authenticate (location, listOfOptions, id) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var options = toObject (listOfOptions); @@ -161,7 +145,7 @@ Elm.Native.ElmFire.Auth.make = function (localRuntime) { if (err) { callback (fbAuthTaskFail (err)); } else { - callback (Task.succeed (auth2elm (auth))); + callback (_elm_lang$core$Native_Scheduler.succeed (auth2elm (auth))); } }; try { @@ -190,34 +174,34 @@ Elm.Native.ElmFire.Auth.make = function (localRuntime) { default: throw ('Bad identification tag.' + pleaseReportThis); } } - catch (exception) { callback (exAuthTaskFail (exception)); } + catch (exception) { callback (exceptionAuthTaskFail (exception)); } } }); } function unauthenticate (location) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location); if (ref) { try { ref.unauth (); } catch (exception) { - callback (exAuthTaskFail (exception)); + callback (exceptionAuthTaskFail (exception)); return; } - callback (Task.succeed (Utils.Tuple0)); + callback (_elm_lang$core$Native_Scheduler.succeed (_elm_lang$core$Native_Utils.Tuple0)); } }); } function userOperation (location, op) { - return Task .asyncFunction (function (callback) { + return _elm_lang$core$Native_Scheduler .nativeBinding (function (callback) { var ref = getRef (location, callback); if (ref) { var onComplete = function (err, res) { if (err) { callback (fbAuthTaskFail (err)); } else { - callback (Task.succeed (asMaybe (res && res.uid))); + callback (_elm_lang$core$Native_Scheduler.succeed (asMaybe (res && res.uid))); } }; try { @@ -240,17 +224,17 @@ Elm.Native.ElmFire.Auth.make = function (localRuntime) { default: throw ('Bad user operation tag.' + pleaseReportThis); } } - catch (exception) { callback (exAuthTaskFail (exception)); } + catch (exception) { callback (exceptionAuthTaskFail (exception)); } } }); } - return localRuntime.Native.ElmFire.Auth.values = - { subscribeAuth: F2 (subscribeAuth) + return { + subscribeAuth: F2 (subscribeAuth) , unsubscribeAuth: unsubscribeAuth , getAuth: getAuth , authenticate: F3 (authenticate) , unauthenticate: unauthenticate , userOperation: F2 (userOperation) }; -}; +} (); From dab65c2542ca2d5486706c6a526beab9c58838b2 Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 13:30:24 +0100 Subject: [PATCH 02/23] Update elm-package.json files for elm 0.17 --- demo/elm-package.json | 32 ++++++++++++++++---------------- elm-package.json | 8 ++++---- example/elm-package.json | 32 ++++++++++++++++---------------- test/elm-package.json | 30 +++++++++++++++--------------- 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/demo/elm-package.json b/demo/elm-package.json index e3ea6e2..32c7d86 100644 --- a/demo/elm-package.json +++ b/demo/elm-package.json @@ -1,17 +1,17 @@ { - "version": "1.0.1", - "summary": "ElmFire demo app", - "repository": "https://github.com/ThomasWeiser/elmfire.git", - "license": "BSD3", - "source-directories": [ - "src", - "../src" - ], - "exposed-modules": [], - "native-modules": true, - "dependencies": { - "elm-lang/core": "2.0.0 <= v < 4.0.0", - "evancz/elm-html": "3.0.0 <= v < 5.0.0" - }, - "elm-version": "0.15.0 <= v < 0.17.0" -} \ No newline at end of file + "version": "2.0.0", + "summary": "ElmFire demo app", + "repository": "https://github.com/ThomasWeiser/elmfire.git", + "license": "BSD3", + "source-directories": [ + "src", + "../src" + ], + "exposed-modules": [], + "native-modules": true, + "dependencies": { + "elm-lang/core": "4.0.0 <= v < 5.0.0", + "elm-lang/html": "1.0.0 <= v < 2.0.0" + }, + "elm-version": "0.17.0 <= v < 0.18.0" +} diff --git a/elm-package.json b/elm-package.json index 2af751b..7511e30 100644 --- a/elm-package.json +++ b/elm-package.json @@ -1,5 +1,5 @@ { - "version": "1.0.7", + "version": "2.0.0", "summary": "Firebase Bindings for Elm", "repository": "https://github.com/ThomasWeiser/elmfire.git", "license": "BSD3", @@ -12,7 +12,7 @@ ], "native-modules": true, "dependencies": { - "elm-lang/core": "3.0.0 <= v < 4.0.0" + "elm-lang/core": "4.0.0 <= v < 5.0.0" }, - "elm-version": "0.16.0 <= v < 0.17.0" -} \ No newline at end of file + "elm-version": "0.17.0 <= v < 0.18.0" +} diff --git a/example/elm-package.json b/example/elm-package.json index 8132268..9115f93 100644 --- a/example/elm-package.json +++ b/example/elm-package.json @@ -1,17 +1,17 @@ { - "version": "1.0.1", - "summary": "ElmFire basic example app", - "repository": "https://github.com/ThomasWeiser/elmfire.git", - "license": "BSD3", - "source-directories": [ - "src", - "../src" - ], - "exposed-modules": [], - "native-modules": true, - "dependencies": { - "elm-lang/core": "2.0.0 <= v < 4.0.0", - "evancz/elm-html": "3.0.0 <= v < 5.0.0" - }, - "elm-version": "0.15.0 <= v < 0.17.0" -} \ No newline at end of file + "version": "2.0.0", + "summary": "ElmFire basic example app", + "repository": "https://github.com/ThomasWeiser/elmfire.git", + "license": "BSD3", + "source-directories": [ + "src", + "../src" + ], + "exposed-modules": [], + "native-modules": true, + "dependencies": { + "elm-lang/core": "4.0.0 <= v < 5.0.0", + "elm-lang/html": "1.0.0 <= v < 2.0.0" + }, + "elm-version": "0.17.0 <= v < 0.18.0" +} diff --git a/test/elm-package.json b/test/elm-package.json index c3d4313..1fce72c 100644 --- a/test/elm-package.json +++ b/test/elm-package.json @@ -1,17 +1,17 @@ { - "version": "1.0.1", - "summary": "ElmFire test app", - "repository": "https://github.com/ThomasWeiser/elmfire.git", - "license": "BSD3", - "source-directories": [ - "src", - "../src" - ], - "exposed-modules": [], - "native-modules": true, - "dependencies": { - "elm-lang/core": "2.0.0 <= v < 4.0.0", - "evancz/elm-html": "3.0.0 <= v < 5.0.0" - }, - "elm-version": "0.15.0 <= v < 0.17.0" + "version": "2.0.0", + "summary": "ElmFire test app", + "repository": "https://github.com/ThomasWeiser/elmfire.git", + "license": "BSD3", + "source-directories": [ + "src", + "../src" + ], + "exposed-modules": [], + "native-modules": true, + "dependencies": { + "elm-lang/core": "4.0.0 <= v < 5.0.0", + "elm-lang/html": "1.0.0 <= v < 2.0.0" + }, + "elm-version": "0.17.0 <= v < 0.18.0" } From 2c804ce07db2ff1a7b4f655b8a0dcb54bec376c7 Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 13:34:26 +0100 Subject: [PATCH 03/23] Update module declaration syntax to elm 0.17 --- src/ElmFire.elm | 4 ++-- src/ElmFire/Auth.elm | 4 ++-- test/src/TaskTest.elm | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ElmFire.elm b/src/ElmFire.elm index 51a417e..f07b70a 100644 --- a/src/ElmFire.elm +++ b/src/ElmFire.elm @@ -1,4 +1,4 @@ -module ElmFire +module ElmFire exposing ( Location , fromUrl, sub, parent, root, push , Reference @@ -24,7 +24,7 @@ module ElmFire , onDisconnectUpdate, onDisconnectRemove, onDisconnectCancel , serverTimeStamp, subscribeServerTimeOffset , Error, ErrorType (..), AuthErrorType (..) - ) where + ) {-| Elm Bindings to Firebase. diff --git a/src/ElmFire/Auth.elm b/src/ElmFire/Auth.elm index fcd9dd7..a5901b8 100644 --- a/src/ElmFire/Auth.elm +++ b/src/ElmFire/Auth.elm @@ -1,4 +1,4 @@ -module ElmFire.Auth +module ElmFire.Auth exposing ( Authentication , getAuth, subscribeAuth, unsubscribeAuth , Identification @@ -10,7 +10,7 @@ module ElmFire.Auth , UserOperation , userOperation , createUser, removeUser, changeEmail, changePassword, resetPassword - ) where + ) {-| Elm bindings to Firebase Authentication. diff --git a/test/src/TaskTest.elm b/test/src/TaskTest.elm index e9ad91d..8638fad 100644 --- a/test/src/TaskTest.elm +++ b/test/src/TaskTest.elm @@ -1,11 +1,11 @@ -module TaskTest +module TaskTest exposing ( testDisplay , runTest , test, sequence , succeeds, fails, equals, meets, errorMeets , clear, createReporter, map, printMapResult, printResult, printString , (|>>), (|>+), (|>-) - ) where + ) {- A Sketch of a testing framework for task-based code. From 4aa82ef66315a50e6810503852e2f2b63a0a3a3c Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 13:47:40 +0100 Subject: [PATCH 04/23] Modify Firebase.js in order to get to access `window` --- src/Native/Firebase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Native/Firebase.js b/src/Native/Firebase.js index f03dc6b..7caba86 100644 --- a/src/Native/Firebase.js +++ b/src/Native/Firebase.js @@ -1,6 +1,6 @@ /*! @license Firebase v2.4.2 License: https://www.firebase.com/terms/terms-of-service.html */ -(function() {var h,n=this;function p(a){return void 0!==a}function aa(){}function ba(a){a.yb=function(){return a.zf?a.zf:a.zf=new a}} +(function() {var h,n=window;function p(a){return void 0!==a}function aa(){}function ba(a){a.yb=function(){return a.zf?a.zf:a.zf=new a}} function ca(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function da(a){return"array"==ca(a)}function ea(a){var b=ca(a);return"array"==b||"object"==b&&"number"==typeof a.length}function q(a){return"string"==typeof a}function fa(a){return"number"==typeof a}function r(a){return"function"==ca(a)}function ga(a){var b=typeof a;return"object"==b&&null!=a||"function"==b}function ha(a,b,c){return a.call.apply(a.bind,arguments)} function ia(a,b,c){if(!a)throw Error();if(2 Date: Tue, 6 Dec 2016 14:11:43 +0100 Subject: [PATCH 05/23] Old (elm-0.16) modules are now "LowLevel" in port to 0.17 --- src/ElmFire/{Auth.elm => Auth/LowLevel.elm} | 4 ++-- src/{ElmFire.elm => ElmFire/LowLevel.elm} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/ElmFire/{Auth.elm => Auth/LowLevel.elm} (98%) rename src/{ElmFire.elm => ElmFire/LowLevel.elm} (99%) diff --git a/src/ElmFire/Auth.elm b/src/ElmFire/Auth/LowLevel.elm similarity index 98% rename from src/ElmFire/Auth.elm rename to src/ElmFire/Auth/LowLevel.elm index a5901b8..ffb415d 100644 --- a/src/ElmFire/Auth.elm +++ b/src/ElmFire/Auth/LowLevel.elm @@ -1,4 +1,4 @@ -module ElmFire.Auth exposing +module ElmFire.Auth.LowLevel exposing ( Authentication , getAuth, subscribeAuth, unsubscribeAuth , Identification @@ -36,7 +36,7 @@ Therefore, only the root of the `Location` parameter is relevant. import Native.Firebase import Native.ElmFire.Auth -import ElmFire exposing (Location, Reference, Error) +import ElmFire.LowLevel exposing (Location, Reference, Error) import Date exposing (Date) import Json.Encode as JE diff --git a/src/ElmFire.elm b/src/ElmFire/LowLevel.elm similarity index 99% rename from src/ElmFire.elm rename to src/ElmFire/LowLevel.elm index f07b70a..cbb0adb 100644 --- a/src/ElmFire.elm +++ b/src/ElmFire/LowLevel.elm @@ -1,4 +1,4 @@ -module ElmFire exposing +module ElmFire.LowLevel exposing ( Location , fromUrl, sub, parent, root, push , Reference From c426f81c48fd1c824ad697d09e0ed9061b0b896b Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 14:15:36 +0100 Subject: [PATCH 06/23] Adopt Example.elm to `ElmFire.LowLevel`. No subscriptions yet. --- example/src/Example.elm | 99 +++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/example/src/Example.elm b/example/src/Example.elm index 397e0b7..a347c52 100644 --- a/example/src/Example.elm +++ b/example/src/Example.elm @@ -7,61 +7,74 @@ Use the displayed link to show the Firebase bashboard for the location. import Html exposing (Html, div, input, output, label, text, a) import Html.Events exposing (on, targetValue) import Html.Attributes exposing (href, target) -import Signal exposing (Signal, Mailbox, mailbox, message) +import Html.App import Task exposing (Task) -import Json.Encode as JE exposing (string, encode) +import Json.Encode as JE +import Json.Decode as JD -import ElmFire exposing - ( fromUrl, set, subscribe, valueChanged, noOrder, noLimit +import ElmFire.LowLevel exposing + ( fromUrl, toUrl, set, subscribe, valueChanged, noOrder, noLimit , Reference, Snapshot, Subscription, Error ) --- You may want to change this url, but you don't have to -url : String -url = "https://elmfire.firebaseio-demo.com/example" +-- Firebase location to access: +-- (You may want to change this url to something you own, but you don't have to) +firebaseUrl : String +firebaseUrl = "https://elmfire.firebaseio-demo.com/example" -values : Mailbox JE.Value -values = mailbox JE.null -inputString : Mailbox String -inputString = mailbox "" +main = + Html.App.program + { init = init + , update = update + , view = view + , subscriptions = subscriptions + } -port runSet : Signal (Task Error Reference) -port runSet = Signal.map - (\str -> set (string str) (fromUrl url)) - inputString.signal -doNothing : a -> Task x () -doNothing = always (Task.succeed ()) +type alias Model = () -- String -port runQuery : Task Error Subscription -port runQuery = - subscribe - (Signal.send values.address << .value) - doNothing - (valueChanged noOrder) - (fromUrl url) -view : JE.Value -> Html -view value = - let outputText = encode 0 value - in +type Msg + = Send String + | Sent (Result Error ()) + + +init : (Model, Cmd Msg) +init = + ( () + , Cmd.none + ) + + +update : Msg -> Model -> (Model, Cmd Msg) +update msg model = + case msg of + Send text -> + ( model + , Task.perform + (Sent << Err) + (Sent << Ok << (always ())) + (set (JE.string text) (fromUrl firebaseUrl)) + ) + Sent result -> + let _ = Debug.log "Sent" result + in + ( model, Cmd.none ) + + +subscriptions : Model -> Sub Msg +subscriptions model = + Sub.none + + +view : Model -> Html Msg +view model = div [] - [ text "ElmFire test at: " - , a [href url, target "_blank"] [text url] - , div [] - [ label [] - [ text "set value: " - , input [ on "input" targetValue (message inputString.address) ] [] - ] - ] - , div [] [ label [] - [ text "query result: " - , output [] [ text outputText ] + [ text "Set value: " + , input + [ on "input" (JD.map Send targetValue) ] + [] ] ] - ] - -main : Signal Html -main = Signal.map view values.signal From 4690a857dfd6fa5ff27d00d717478abae948bf86 Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 14:18:11 +0100 Subject: [PATCH 07/23] Apply elm-format --- demo/src/Demo.elm | 510 ++++++++++++++-------- example/src/Example.elm | 111 +++-- src/ElmFire/Auth/LowLevel.elm | 268 ++++++++---- src/ElmFire/LowLevel.elm | 676 +++++++++++++++++++---------- test/src/TaskTest.elm | 399 ++++++++++------- test/src/Tests.elm | 791 +++++++++++++++++----------------- 6 files changed, 1662 insertions(+), 1093 deletions(-) diff --git a/demo/src/Demo.elm b/demo/src/Demo.elm index a0568a4..795d3fd 100644 --- a/demo/src/Demo.elm +++ b/demo/src/Demo.elm @@ -1,290 +1,436 @@ {- A ElmFire Demo App -A given sequence of tasks is run on the Firebase API. -Steps and results are logged as Html. + A given sequence of tasks is run on the Firebase API. + Steps and results are logged as Html. -This is work in progress. -We aim to make the logging output look much more nicer. + This is work in progress. + We aim to make the logging output look much more nicer. -} + +module Main exposing (..) + import Signal exposing (Signal, Mailbox, mailbox, message) import Task exposing (Task, andThen, onError, fail, succeed, sleep) import Json.Encode as JE import Time import Html exposing (Html, div, span, input, output, label, text, a, h1, h2) import Html.Attributes exposing (href, target, class) +import ElmFire + exposing + ( fromUrl + , toUrl + , key + , sub + , parent + , root + , push + , location + , open + , set + , setWithPriority + , setPriority + , update + , remove + , subscribe + , unsubscribe + , once + , valueChanged + , childAdded + , childChanged + , childRemoved + , childMoved + , noOrder + , noLimit + , Location + , Reference + , Priority(..) + , Cancellation(..) + , Snapshot + , Subscription + , Error + , Query + ) -import ElmFire exposing - ( fromUrl, toUrl, key, sub, parent, root, push, location, open - , set, setWithPriority, setPriority, update, remove - , subscribe, unsubscribe, once - , valueChanged, childAdded, childChanged, childRemoved, childMoved - , noOrder, noLimit - , Location, Reference, Priority (..), Cancellation (..) - , Snapshot, Subscription, Error, Query - ) ------------------------------------------------------------------------------- + url : String -url = "https://elmfire.firebaseio-demo.com/demo" +url = + "https://elmfire.firebaseio-demo.com/demo" + + ------------------------------------------------------------------------------- + type Response - = NoResponse - | Data Snapshot - | Canceled Cancellation + = NoResponse + | Data Snapshot + | Canceled Cancellation + responses : Signal.Mailbox Response -responses = Signal.mailbox NoResponse +responses = + Signal.mailbox NoResponse + type LogEntry - = LogNone - | LogTaskStart String - | LogTaskSuccess String String - | LogTaskFailure String String - | LogResponse Response + = LogNone + | LogTaskStart String + | LogTaskSuccess String String + | LogTaskFailure String String + | LogResponse Response + notes : Signal.Mailbox LogEntry -notes = Signal.mailbox LogNone +notes = + Signal.mailbox LogNone + logEntries : Signal LogEntry logEntries = - Signal.merge - notes.signal - (Signal.map LogResponse responses.signal) + Signal.merge + notes.signal + (Signal.map LogResponse responses.signal) + + +type alias LogList = + List LogEntry + + +type alias TaskList = + List ( String, LogEntry ) -type alias LogList = List LogEntry -type alias TaskList = List (String, LogEntry) type alias Model = - { log: LogList - , tasks: TaskList - } + { log : LogList + , tasks : TaskList + } + startModel : Model -startModel = { log = [], tasks = [] } +startModel = + { log = [], tasks = [] } + progression : LogEntry -> Model -> Model progression note model = - { log = note :: model.log - , tasks = - case note of - LogTaskStart step -> replaceOrAppend step note model.tasks - LogTaskSuccess step _ -> replaceOrAppend step note model.tasks - LogTaskFailure step _ -> replaceOrAppend step note model.tasks - otherwise -> model.tasks - } + { log = note :: model.log + , tasks = + case note of + LogTaskStart step -> + replaceOrAppend step note model.tasks + + LogTaskSuccess step _ -> + replaceOrAppend step note model.tasks + + LogTaskFailure step _ -> + replaceOrAppend step note model.tasks + + otherwise -> + model.tasks + } + replaceOrAppend : String -> LogEntry -> TaskList -> TaskList replaceOrAppend step note tasks = - case tasks of - [] -> [(step, note)] - (s1, n1) :: rest -> - if s1 == step - then (step, note) :: rest - else (s1, n1) :: replaceOrAppend step note rest + case tasks of + [] -> + [ ( step, note ) ] + + ( s1, n1 ) :: rest -> + if s1 == step then + ( step, note ) :: rest + else + ( s1, n1 ) :: replaceOrAppend step note rest + state : Signal Model -state = Signal.foldp progression startModel logEntries +state = + Signal.foldp progression startModel logEntries + view : Model -> Html view model = - div [] - [ h1 [] [text "ElmFire Demo"] - , div [] [ a [href url, target "_blank"] [text url] ] - , div [class "tasks"] ( h2 [] [text "Tasks"] :: viewTasks model.tasks) - , div [class "logs"] ( h2 [] [text "Log"] :: viewLog model.log ) - ] + div [] + [ h1 [] [ text "ElmFire Demo" ] + , div [] [ a [ href url, target "_blank" ] [ text url ] ] + , div [ class "tasks" ] (h2 [] [ text "Tasks" ] :: viewTasks model.tasks) + , div [ class "logs" ] (h2 [] [ text "Log" ] :: viewLog model.log) + ] + viewLog : LogList -> List Html -viewLog log = List.foldl -- reverses the list for display - (\entry htmlList -> - let maybeHtml = viewLogEntry entry in - case maybeHtml of - Nothing -> htmlList - Just html -> html::htmlList - ) - [] - log +viewLog log = + List.foldl + -- reverses the list for display + (\entry htmlList -> + let + maybeHtml = + viewLogEntry entry + in + case maybeHtml of + Nothing -> + htmlList + + Just html -> + html :: htmlList + ) + [] + log + viewTasks : TaskList -> List Html -viewTasks = List.map - (\(step, logEntry) -> - div [class "line"] - [ span [] [text step] - , case logEntry of - LogTaskStart _ -> span [class "started"] [text "..."] - LogTaskSuccess _ res -> span [class "success"] [text res] - LogTaskFailure _ err -> span [class "failure"] [text err] - otherwise -> text "" - - ] - ) +viewTasks = + List.map + (\( step, logEntry ) -> + div [ class "line" ] + [ span [] [ text step ] + , case logEntry of + LogTaskStart _ -> + span [ class "started" ] [ text "..." ] + + LogTaskSuccess _ res -> + span [ class "success" ] [ text res ] + + LogTaskFailure _ err -> + span [ class "failure" ] [ text err ] + + otherwise -> + text "" + ] + ) + viewLogEntry : LogEntry -> Maybe Html viewLogEntry logEntry = - let - line c s t = - div [class "line"] [ span [] [text s], span [class c] [text t] ] - in case logEntry of - LogNone -> Nothing - LogTaskStart step -> - Just <| line "started" step "started" - LogTaskSuccess step res -> - Just <| line "success" step res - LogTaskFailure step err -> - Just <| line "failure" step err - LogResponse response -> - case response of - NoResponse -> Nothing - Data snapshot -> - Just <| line "response" (toString snapshot.subscription) (viewSnapshot snapshot) - Canceled (cancellation) -> - Just <| case cancellation of - Unsubscribed id -> - line "canceled" (toString id) "unsubscribed" - QueryError id err -> - line "canceled" (toString id) ("queryError: " ++ toString err) + let + line c s t = + div [ class "line" ] [ span [] [ text s ], span [ class c ] [ text t ] ] + in + case logEntry of + LogNone -> + Nothing + + LogTaskStart step -> + Just <| line "started" step "started" + + LogTaskSuccess step res -> + Just <| line "success" step res + + LogTaskFailure step err -> + Just <| line "failure" step err + + LogResponse response -> + case response of + NoResponse -> + Nothing + + Data snapshot -> + Just <| line "response" (toString snapshot.subscription) (viewSnapshot snapshot) + + Canceled cancellation -> + Just <| + case cancellation of + Unsubscribed id -> + line "canceled" (toString id) "unsubscribed" + + QueryError id err -> + line "canceled" (toString id) ("queryError: " ++ toString err) + viewSnapshot : Snapshot -> String viewSnapshot snapshot = - let k = key snapshot.reference in - (if k == "" then "(root)" else k) ++ ": " ++ (viewValue snapshot.value) + let + k = + key snapshot.reference + in + (if k == "" then + "(root)" + else + k + ) + ++ ": " + ++ (viewValue snapshot.value) + viewValue : JE.Value -> String viewValue value = - -- Comparing JE.null throws a runtime error, - -- see https://github.com/elm-lang/core/pull/294 - -- if JE.null == value then "no value" else JE.encode 0 value - JE.encode 0 value + -- Comparing JE.null throws a runtime error, + -- see https://github.com/elm-lang/core/pull/294 + -- if JE.null == value then "no value" else JE.encode 0 value + JE.encode 0 value + main : Signal Html -main = Signal.map view state +main = + Signal.map view state + + ------------------------------------------------------------------------------- + intercept : (v -> String) -> String -> Task Error v -> Task Error v intercept valueToString step task = - Signal.send notes.address (LogTaskStart step) - `andThen` \_ -> - ( task - `onError` \err -> - Signal.send notes.address (LogTaskFailure step (toString err)) - `andThen` \_ -> - fail err - ) - `andThen` \val -> - Signal.send notes.address (LogTaskSuccess step (valueToString val)) - `andThen` \_ -> - succeed val + Signal.send notes.address (LogTaskStart step) + `andThen` + \_ -> + (task + `onError` + \err -> + Signal.send notes.address (LogTaskFailure step (toString err)) + `andThen` + \_ -> + fail err + ) + `andThen` + \val -> + Signal.send notes.address (LogTaskSuccess step (valueToString val)) + `andThen` + \_ -> + succeed val + + ------------------------------------------------------------------------------- + doSet : String -> JE.Value -> Location -> Task Error Reference doSet step value location = - intercept (always "synced") step (set value location) + intercept (always "synced") step (set value location) + doSetPriority : String -> Priority -> Location -> Task Error Reference doSetPriority step priority location = - intercept (always "synced") step (setPriority priority location) + intercept (always "synced") step (setPriority priority location) + doUpdate : String -> JE.Value -> Location -> Task Error Reference doUpdate step value location = - intercept (always "synced") step (update value location) + intercept (always "synced") step (update value location) + doRemove : String -> Location -> Task Error Reference doRemove step location = - intercept (always "synced") step (remove location) + intercept (always "synced") step (remove location) + doSubscribe : String -> Query -> Location -> Task Error Subscription doSubscribe step query location = - intercept toString step - ( subscribe - (Signal.send responses.address << Data) - (Signal.send responses.address << Canceled) - query - location - ) + intercept toString + step + (subscribe + (Signal.send responses.address << Data) + (Signal.send responses.address << Canceled) + query + location + ) + doUnsubscribe : String -> Subscription -> Task Error () doUnsubscribe step subscription = - intercept (always "done") step (unsubscribe subscription) + intercept (always "done") step (unsubscribe subscription) + doOnce : String -> Query -> Location -> Task Error JE.Value doOnce step query location = - intercept viewValue step - ( once query location - `andThen` \snapshot -> succeed snapshot.value - ) + intercept viewValue + step + (once query location + `andThen` \snapshot -> succeed snapshot.value + ) + doSleep : String -> Float -> Task () () doSleep id seconds = - let step = "sleep " ++ id ++ " for " ++ (toString seconds) ++ " seconds" in - Signal.send notes.address (LogTaskStart step) - `andThen` \_ -> sleep (seconds * Time.second) - `andThen` \_ -> Signal.send notes.address (LogTaskSuccess step "awake") + let + step = + "sleep " ++ id ++ " for " ++ (toString seconds) ++ " seconds" + in + Signal.send notes.address (LogTaskStart step) + `andThen` + \_ -> + sleep (seconds * Time.second) + `andThen` \_ -> Signal.send notes.address (LogTaskSuccess step "awake") + doShowRefLocation : String -> Reference -> Task e () doShowRefLocation id ref = - Signal.send notes.address (LogTaskSuccess id (location ref |> toString)) + Signal.send notes.address (LogTaskSuccess id (location ref |> toString)) + doRefUrl : String -> Reference -> Task e () doRefUrl id ref = - Signal.send notes.address (LogTaskSuccess id (toUrl ref)) + Signal.send notes.address (LogTaskSuccess id (toUrl ref)) + doRefKey : String -> Reference -> Task e () doRefKey id ref = - Signal.send notes.address (LogTaskSuccess id (key ref)) + Signal.send notes.address (LogTaskSuccess id (key ref)) + + ------------------------------------------------------------------------------- + andAnyway : Task x a -> Task y b -> Task y b andAnyway task1 task2 = - (Task.map (\_ -> ()) task1 `onError` (\_ -> succeed ())) - `andThen` (\_ -> task2) + (Task.map (\_ -> ()) task1 `onError` (\_ -> succeed ())) + `andThen` (\_ -> task2) + port runTasks : Task () () port runTasks = - let loc = fromUrl url in - doSubscribe "query1 value" (valueChanged noOrder) loc - `andAnyway` (Task.spawn <| doSet "async set1 value" (JE.string "start") loc) - `andAnyway` doSubscribe "query2 parent value" - (valueChanged noOrder) - (loc |> parent) - `andAnyway` doSleep "1" 2 - `andAnyway` open (push loc) - `andThen` ( \ref -> - doRefUrl "opened url" ref - `andAnyway` doRefKey "opened key" ref - ) - `andAnyway` doSet "set2 value" (JE.string "hello") loc - `andAnyway` open (loc |> root) - `andAnyway` open (loc |> root |> parent) - `andAnyway` doSubscribe "query3 child added" (childAdded noOrder) loc - `andAnyway` doSubscribe "query4 child changed" (childChanged noOrder) loc - `andAnyway` doSubscribe "query5 child removed" (childRemoved noOrder) loc - `andAnyway` doSubscribe "query6 child moved" (childMoved noOrder) loc - `andAnyway` doSleep "2" 2 - `andAnyway` doSet "set3 object value" - (JE.object [("a", (JE.string "hello")), ("b", (JE.string "Elm"))]) - loc - `andAnyway` doSleep "3" 2 - `andAnyway` doSet "set4 add child" (JE.string "at Firebase") (loc |> sub "c") - `andAnyway` doSleep "4" 2 - `andAnyway` ( doSubscribe "subscribe" (valueChanged noOrder) loc - `andThen` \subscription -> doUnsubscribe "unsubscribe" subscription - ) - `andAnyway` doOnce "query once" (valueChanged noOrder) loc - `andAnyway` doRemove "remove child" (loc |> sub "b") - `andAnyway` doUpdate "update object a and d" - (JE.object [("a", (JE.string "Hello")), ("d", (JE.string "Elmies"))]) - loc - `andAnyway` ( open (loc |> sub "e" |> push) - `andThen` - \ref -> doSet "push set" (JE.string <| key ref) (location ref) - `andThen` - \ref -> doSetPriority "setPriority" (NumberPriority 17) (location ref) - ) - `andAnyway` succeed () + let + loc = + fromUrl url + in + doSubscribe "query1 value" (valueChanged noOrder) loc + `andAnyway` (Task.spawn <| doSet "async set1 value" (JE.string "start") loc) + `andAnyway` + doSubscribe "query2 parent value" + (valueChanged noOrder) + (loc |> parent) + `andAnyway` doSleep "1" 2 + `andAnyway` open (push loc) + `andThen` + (\ref -> + doRefUrl "opened url" ref + `andAnyway` doRefKey "opened key" ref + ) + `andAnyway` doSet "set2 value" (JE.string "hello") loc + `andAnyway` open (loc |> root) + `andAnyway` open (loc |> root |> parent) + `andAnyway` doSubscribe "query3 child added" (childAdded noOrder) loc + `andAnyway` doSubscribe "query4 child changed" (childChanged noOrder) loc + `andAnyway` doSubscribe "query5 child removed" (childRemoved noOrder) loc + `andAnyway` doSubscribe "query6 child moved" (childMoved noOrder) loc + `andAnyway` doSleep "2" 2 + `andAnyway` + doSet "set3 object value" + (JE.object [ ( "a", (JE.string "hello") ), ( "b", (JE.string "Elm") ) ]) + loc + `andAnyway` doSleep "3" 2 + `andAnyway` doSet "set4 add child" (JE.string "at Firebase") (loc |> sub "c") + `andAnyway` doSleep "4" 2 + `andAnyway` + (doSubscribe "subscribe" (valueChanged noOrder) loc + `andThen` \subscription -> doUnsubscribe "unsubscribe" subscription + ) + `andAnyway` doOnce "query once" (valueChanged noOrder) loc + `andAnyway` doRemove "remove child" (loc |> sub "b") + `andAnyway` + doUpdate "update object a and d" + (JE.object [ ( "a", (JE.string "Hello") ), ( "d", (JE.string "Elmies") ) ]) + loc + `andAnyway` + (open (loc |> sub "e" |> push) + `andThen` + \ref -> + doSet "push set" (JE.string <| key ref) (location ref) + `andThen` \ref -> doSetPriority "setPriority" (NumberPriority 17) (location ref) + ) + `andAnyway` succeed () diff --git a/example/src/Example.elm b/example/src/Example.elm index a347c52..4c53f10 100644 --- a/example/src/Example.elm +++ b/example/src/Example.elm @@ -1,9 +1,13 @@ {- Basic ElmFire Example App -Write the text from a input field to a Firebase location. -Query that same location and display the result. -Use the displayed link to show the Firebase bashboard for the location. + Write the text from a input field to a Firebase location. + Query that same location and display the result. + Use the displayed link to show the Firebase bashboard for the location. -} + + +module Main exposing (..) + import Html exposing (Html, div, input, output, label, text, a) import Html.Events exposing (on, targetValue) import Html.Attributes exposing (href, target) @@ -11,70 +15,91 @@ import Html.App import Task exposing (Task) import Json.Encode as JE import Json.Decode as JD +import ElmFire.LowLevel + exposing + ( fromUrl + , toUrl + , set + , subscribe + , valueChanged + , noOrder + , noLimit + , Reference + , Snapshot + , Subscription + , Error + ) -import ElmFire.LowLevel exposing - ( fromUrl, toUrl, set, subscribe, valueChanged, noOrder, noLimit - , Reference, Snapshot, Subscription, Error - ) -- Firebase location to access: -- (You may want to change this url to something you own, but you don't have to) + + firebaseUrl : String -firebaseUrl = "https://elmfire.firebaseio-demo.com/example" +firebaseUrl = + "https://elmfire.firebaseio-demo.com/example" main = - Html.App.program - { init = init - , update = update - , view = view - , subscriptions = subscriptions - } + Html.App.program + { init = init + , update = update + , view = view + , subscriptions = subscriptions + } + + +type alias Model = + () + -type alias Model = () -- String +-- String type Msg - = Send String - | Sent (Result Error ()) + = Send String + | Sent (Result Error ()) -init : (Model, Cmd Msg) +init : ( Model, Cmd Msg ) init = - ( () - , Cmd.none - ) + ( () + , Cmd.none + ) -update : Msg -> Model -> (Model, Cmd Msg) +update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = - case msg of - Send text -> - ( model - , Task.perform - (Sent << Err) - (Sent << Ok << (always ())) - (set (JE.string text) (fromUrl firebaseUrl)) - ) - Sent result -> - let _ = Debug.log "Sent" result - in - ( model, Cmd.none ) + case msg of + Send text -> + ( model + , Task.perform + (Sent << Err) + (Sent << Ok << (always ())) + (set (JE.string text) (fromUrl firebaseUrl)) + ) + + Sent result -> + let + _ = + Debug.log "Sent" result + in + ( model, Cmd.none ) subscriptions : Model -> Sub Msg subscriptions model = - Sub.none + Sub.none view : Model -> Html Msg view model = - div [] - [ label [] - [ text "Set value: " - , input - [ on "input" (JD.map Send targetValue) ] - [] - ] - ] + div [] + [ label [] + [ text "Set value: " + , input + [ on "input" (JD.map Send targetValue) ] + [] + ] + ] diff --git a/src/ElmFire/Auth/LowLevel.elm b/src/ElmFire/Auth/LowLevel.elm index ffb415d..f9d79e8 100644 --- a/src/ElmFire/Auth/LowLevel.elm +++ b/src/ElmFire/Auth/LowLevel.elm @@ -1,17 +1,31 @@ -module ElmFire.Auth.LowLevel exposing - ( Authentication - , getAuth, subscribeAuth, unsubscribeAuth - , Identification - , authenticate, unauthenticate - , asAnonymous, withPassword, withOAuthPopup, withOAuthRedirect - , withOAuthAccessToken, withOAuthCredentials, withCustomToken - , Options - , rememberDefault, rememberSessionOnly, rememberNone - , UserOperation - , userOperation - , createUser, removeUser, changeEmail, changePassword, resetPassword - ) - +module ElmFire.Auth.LowLevel + exposing + ( Authentication + , getAuth + , subscribeAuth + , unsubscribeAuth + , Identification + , authenticate + , unauthenticate + , asAnonymous + , withPassword + , withOAuthPopup + , withOAuthRedirect + , withOAuthAccessToken + , withOAuthCredentials + , withCustomToken + , Options + , rememberDefault + , rememberSessionOnly + , rememberNone + , UserOperation + , userOperation + , createUser + , removeUser + , changeEmail + , changePassword + , resetPassword + ) {-| Elm bindings to Firebase Authentication. @@ -37,70 +51,104 @@ Therefore, only the root of the `Location` parameter is relevant. import Native.Firebase import Native.ElmFire.Auth import ElmFire.LowLevel exposing (Location, Reference, Error) - import Date exposing (Date) import Json.Encode as JE import Task exposing (Task) -{-| Authentication data. See Firebase doc for details. -} + +{-| Authentication data. See Firebase doc for details. +-} type alias Authentication = - { uid: String - , provider: String - , token: String - , expires: Date - , auth: JE.Value - , specifics: JE.Value - } - -{-| Subscribe to changes to the client's authentication state -} + { uid : String + , provider : String + , token : String + , expires : Date + , auth : JE.Value + , specifics : JE.Value + } + + +{-| Subscribe to changes to the client's authentication state +-} subscribeAuth : (Maybe Authentication -> Task x a) -> Location -> Task Error () -subscribeAuth = Native.ElmFire.Auth.subscribeAuth +subscribeAuth = + Native.ElmFire.Auth.subscribeAuth -{-| Quit subscription to authentication state -} + +{-| Quit subscription to authentication state +-} unsubscribeAuth : Location -> Task Error () -unsubscribeAuth = Native.ElmFire.Auth.unsubscribeAuth +unsubscribeAuth = + Native.ElmFire.Auth.unsubscribeAuth + -{-| Retrieve the current authentication state of the client -} +{-| Retrieve the current authentication state of the client +-} getAuth : Location -> Task Error (Maybe Authentication) -getAuth = Native.ElmFire.Auth.getAuth +getAuth = + Native.ElmFire.Auth.getAuth + -{-| Identification options to authenticate at a Firebase -} +{-| Identification options to authenticate at a Firebase +-} type Identification - = Anonymous - | Password String String - | OAuthPopup String - | OAuthRedirect String - | OAuthAccessToken String String - | OAuthCredentials String (List (String, String)) - | CustomToken String - -{-| Identify as a anonymous, temporary guest -} + = Anonymous + | Password String String + | OAuthPopup String + | OAuthRedirect String + | OAuthAccessToken String String + | OAuthCredentials String (List ( String, String )) + | CustomToken String + + +{-| Identify as a anonymous, temporary guest +-} asAnonymous : Identification -asAnonymous = Anonymous +asAnonymous = + Anonymous + -{-| Identify with email and password -} +{-| Identify with email and password +-} withPassword : String -> String -> Identification -withPassword = Password +withPassword = + Password + -{-| Identify using a popup-based OAuth flow -} +{-| Identify using a popup-based OAuth flow +-} withOAuthPopup : String -> Identification -withOAuthPopup = OAuthPopup +withOAuthPopup = + OAuthPopup -{-| Identify using a redirect-based OAuth flow -} + +{-| Identify using a redirect-based OAuth flow +-} withOAuthRedirect : String -> Identification -withOAuthRedirect = OAuthRedirect +withOAuthRedirect = + OAuthRedirect + -{-| Identify using OAuth access token -} +{-| Identify using OAuth access token +-} withOAuthAccessToken : String -> String -> Identification -withOAuthAccessToken = OAuthAccessToken +withOAuthAccessToken = + OAuthAccessToken + + +{-| Identify using OAuth credentials +-} +withOAuthCredentials : String -> List ( String, String ) -> Identification +withOAuthCredentials = + OAuthCredentials -{-| Identify using OAuth credentials -} -withOAuthCredentials : String -> List (String, String) -> Identification -withOAuthCredentials = OAuthCredentials -{-| Identify using an authentication token or Firebase secret -} +{-| Identify using an authentication token or Firebase secret +-} withCustomToken : String -> Identification -withCustomToken = CustomToken +withCustomToken = + CustomToken + {-| Optional authentication parameters @@ -108,76 +156,118 @@ All providers allow option `remember` to specify the presistency of authenticati Specific provider may accept additional options. See Firebase docs. -} -type alias Options = List (String, String) +type alias Options = + List ( String, String ) + {-| Option for default persistence: Sessions are persisted for as long as it is configured in the Firebase's dashboard. -} -rememberDefault : (String, String) -rememberDefault = ("remember", "default") +rememberDefault : ( String, String ) +rememberDefault = + ( "remember", "default" ) + {-| Option for session only persistence: Persistence is limited to the lifetime of the current window. -} -rememberSessionOnly : (String, String) -rememberSessionOnly = ("remember", "sessionOnly") +rememberSessionOnly : ( String, String ) +rememberSessionOnly = + ( "remember", "sessionOnly" ) + {-| Option for no persistence: No persistent authentication data is used. End authentication as soon as the page is closed. -} -rememberNone : (String, String) -rememberNone = ("remember", "none") +rememberNone : ( String, String ) +rememberNone = + ( "remember", "none" ) -{-| Authenticate client at a Firebase -} -authenticate : Location - -> Options - -> Identification - -> Task Error Authentication -authenticate = Native.ElmFire.Auth.authenticate -{-| Unauthenticate client at a Firebase -} +{-| Authenticate client at a Firebase +-} +authenticate : + Location + -> Options + -> Identification + -> Task Error Authentication +authenticate = + Native.ElmFire.Auth.authenticate + + +{-| Unauthenticate client at a Firebase +-} unauthenticate : Location -> Task Error () -unauthenticate = Native.ElmFire.Auth.unauthenticate +unauthenticate = + Native.ElmFire.Auth.unauthenticate -{-| Specification of a user management operation -} + +{-| Specification of a user management operation +-} type UserOperation - = CreateUser String String -- email password - | RemoveUser String String -- email password - | ChangeEmail String String String -- email password newEmail - | ChangePassword String String String -- email password newPassword - | ResetPassword String -- email + = CreateUser String String + -- email password + | RemoveUser String String + -- email password + | ChangeEmail String String String + -- email password newEmail + | ChangePassword String String String + -- email password newPassword + | ResetPassword String + + + +-- email + {-| UserOperation: Create a user identity. -Parameter: email password -} +Parameter: email password +-} createUser : String -> String -> UserOperation -createUser = CreateUser +createUser = + CreateUser + {-| UserOperation: Remove a user identity. -Parameter: email password -} +Parameter: email password +-} removeUser : String -> String -> UserOperation -removeUser = RemoveUser +removeUser = + RemoveUser + {-| UserOperation: Change the email address of a user identity. -Parameter: oldEmail password newEmail -} +Parameter: oldEmail password newEmail +-} changeEmail : String -> String -> String -> UserOperation -changeEmail = ChangeEmail +changeEmail = + ChangeEmail + {-| UserOperation: Change the password of a user identity. -Parameter: email oldPassword newPassword -} +Parameter: email oldPassword newPassword +-} changePassword : String -> String -> String -> UserOperation -changePassword = ChangePassword +changePassword = + ChangePassword + {-| UserOperation: Initiate a password reset. Firebase will send an appropriate email to the account owner. -Parameter: email -} +Parameter: email +-} resetPassword : String -> UserOperation -resetPassword = ResetPassword +resetPassword = + ResetPassword + {-| Perform a user management operation at a Firebase Operation `createUser` returns a `Just uid` on success, all other operations return `Nothing` on success. -} -userOperation : Location - -> UserOperation - -> Task Error (Maybe String) -userOperation = Native.ElmFire.Auth.userOperation +userOperation : + Location + -> UserOperation + -> Task Error (Maybe String) +userOperation = + Native.ElmFire.Auth.userOperation diff --git a/src/ElmFire/LowLevel.elm b/src/ElmFire/LowLevel.elm index cbb0adb..739d700 100644 --- a/src/ElmFire/LowLevel.elm +++ b/src/ElmFire/LowLevel.elm @@ -1,31 +1,71 @@ -module ElmFire.LowLevel exposing - ( Location - , fromUrl, sub, parent, root, push - , Reference - , open, key, toUrl, location - , Priority (..) - , set, setWithPriority, setPriority, update, remove - , Snapshot - , Action (..) - , transaction - , Query, OrderOptions, RangeOptions, LimitOptions - , Subscription - , Cancellation (..) - , subscribe, unsubscribe, once - , valueChanged, childAdded, childChanged, childRemoved, childMoved - , noOrder, orderByChild, orderByValue, orderByKey, orderByPriority - , noRange, startAt, endAt, range, equalTo - , noLimit, limitToFirst, limitToLast - , toSnapshotList, toValueList, toKeyList,toPairList - , exportValue - , goOffline, goOnline - , subscribeConnected - , onDisconnectSet, onDisconnectSetWithPriority - , onDisconnectUpdate, onDisconnectRemove, onDisconnectCancel - , serverTimeStamp, subscribeServerTimeOffset - , Error, ErrorType (..), AuthErrorType (..) - ) - +module ElmFire.LowLevel + exposing + ( Location + , fromUrl + , sub + , parent + , root + , push + , Reference + , open + , key + , toUrl + , location + , Priority(..) + , set + , setWithPriority + , setPriority + , update + , remove + , Snapshot + , Action(..) + , transaction + , Query + , OrderOptions + , RangeOptions + , LimitOptions + , Subscription + , Cancellation(..) + , subscribe + , unsubscribe + , once + , valueChanged + , childAdded + , childChanged + , childRemoved + , childMoved + , noOrder + , orderByChild + , orderByValue + , orderByKey + , orderByPriority + , noRange + , startAt + , endAt + , range + , equalTo + , noLimit + , limitToFirst + , limitToLast + , toSnapshotList + , toValueList + , toKeyList + , toPairList + , exportValue + , goOffline + , goOnline + , subscribeConnected + , onDisconnectSet + , onDisconnectSetWithPriority + , onDisconnectUpdate + , onDisconnectRemove + , onDisconnectCancel + , serverTimeStamp + , subscribeServerTimeOffset + , Error + , ErrorType(..) + , AuthErrorType(..) + ) {-| Elm Bindings to Firebase. @@ -78,7 +118,6 @@ ElmFire maps the Firebase JavaScript API to Elm functions and tasks. @docs Error, ErrorType, AuthErrorType -} - import Native.Firebase import Native.ElmFire import Time exposing (Time) @@ -87,42 +126,48 @@ import Json.Decode as JD import Task exposing (Task) -{-| Errors reported from Firebase or ElmFire -} +{-| Errors reported from Firebase or ElmFire +-} type alias Error = - { tag: ErrorType - , description: String - } + { tag : ErrorType + , description : String + } + -{-| Type of errors reported from Firebase or ElmFire -} +{-| Type of errors reported from Firebase or ElmFire +-} type ErrorType - = LocationError - | PermissionError - | UnavailableError - | TooBigError - | OtherFirebaseError - | AuthError AuthErrorType - | UnknownSubscription - -{-| Errors reported from Authentication Module -} + = LocationError + | PermissionError + | UnavailableError + | TooBigError + | OtherFirebaseError + | AuthError AuthErrorType + | UnknownSubscription + + +{-| Errors reported from Authentication Module +-} type AuthErrorType - = AuthenticationDisabled - | EmailTaken - | InvalidArguments - | InvalidConfiguration - | InvalidCredentials - | InvalidEmail - | InvalidOrigin - | InvalidPassword - | InvalidProvider - | InvalidToken - | InvalidUser - | NetworkError - | ProviderError - | TransportUnavailable - | UnknownError - | UserCancelled - | UserDenied - | OtherAuthenticationError + = AuthenticationDisabled + | EmailTaken + | InvalidArguments + | InvalidConfiguration + | InvalidCredentials + | InvalidEmail + | InvalidOrigin + | InvalidPassword + | InvalidProvider + | InvalidToken + | InvalidUser + | NetworkError + | ProviderError + | TransportUnavailable + | UnknownError + | UserCancelled + | UserDenied + | OtherAuthenticationError + {-| A Firebase location, which is an opaque type that represents a literal path into a firebase. @@ -136,18 +181,21 @@ Locations are generally unvalidated until their use in a task. The constructor functions are pure. -} type Location - = UrlLocation String - | SubLocation String Location - | ParentLocation Location - | RootLocation Location - | PushLocation Location - | RefLocation Reference + = UrlLocation String + | SubLocation String Location + | ParentLocation Location + | RootLocation Location + | PushLocation Location + | RefLocation Reference + {-| A Firebase reference, which is an opaque type that represents an opened path. References are returned from many Firebase tasks as well as in query results. -} -type Reference = Reference +type Reference + = Reference + {-| Each existing location in a Firebase may be attributed with a priority, which can be a number or a string. @@ -155,17 +203,23 @@ which can be a number or a string. Priorities can be used for filtering and sorting entries in a query. -} type Priority - = NoPriority - | NumberPriority Float - | StringPriority String + = NoPriority + | NumberPriority Float + | StringPriority String -{-| Unique opaque identifier for running subscriptions -} -type Subscription = Subscription -{-| Message about cancelled query -} +{-| Unique opaque identifier for running subscriptions +-} +type Subscription + = Subscription + + +{-| Message about cancelled query +-} type Cancellation - = Unsubscribed Subscription - | QueryError Subscription Error + = Unsubscribed Subscription + | QueryError Subscription Error + {-| Message about a received value. @@ -178,52 +232,68 @@ type Cancellation - `priority` returns the given priority of the data. -} type alias Snapshot = - { subscription: Subscription - , key: String - , reference: Reference - , existing: Bool - , value: JE.Value - , prevKey: Maybe String - , priority: Priority - , intern_: SnapshotFB - } + { subscription : Subscription + , key : String + , reference : Reference + , existing : Bool + , value : JE.Value + , prevKey : Maybe String + , priority : Priority + , intern_ : SnapshotFB + } + + {- A Firebase snapshot as an internally used JS object -} -type SnapshotFB = SnapshotFB -{-| Possible return values for update functions of a transaction -} + +type SnapshotFB + = SnapshotFB + + +{-| Possible return values for update functions of a transaction +-} type Action - = Abort - | Remove - | Set JE.Value + = Abort + | Remove + | Set JE.Value + {-| Construct a new location from a full Firebase URL. loc = fromUrl "https://elmfire.firebaseio-demo.com/foo/bar" -} fromUrl : String -> Location -fromUrl = UrlLocation +fromUrl = + UrlLocation + {-| Construct a location for the descendant at the specified relative path. locUsers = sub "users" loc -} sub : String -> Location -> Location -sub = SubLocation +sub = + SubLocation + {-| Construct the parent location from a child location. loc2 = parent loc1 -} parent : Location -> Location -parent = ParentLocation +parent = + ParentLocation + {-| Construct the root location from descendant location loc2 = root loc1 -} root : Location -> Location -root = RootLocation +root = + RootLocation + {-| Construct a new child location using a to-be-generated key. @@ -238,18 +308,25 @@ and get its name. set val (push loc) `andThen` (\ref -> ... key ref ...) -} push : Location -> Location -push = PushLocation +push = + PushLocation + {-| Obtain a location from a reference. reference = location loc -} location : Reference -> Location -location = RefLocation +location = + RefLocation + -{-| Get the url of a reference. -} +{-| Get the url of a reference. +-} toUrl : Reference -> String -toUrl = Native.ElmFire.toUrl +toUrl = + Native.ElmFire.toUrl + {-| Get the key of a reference. @@ -257,7 +334,9 @@ The last token in a Firebase location is considered its key. It's the empty string for the root. -} key : Reference -> String -key = Native.ElmFire.key +key = + Native.ElmFire.key + {-| Actually open a location, which results in a reference (if the location is valid). @@ -272,7 +351,9 @@ The task fails if the location construct is invalid. `andThen` (\ref -> Signal.send userRefCache.address (user, ref)) -} open : Location -> Task Error Reference -open = Native.ElmFire.open +open = + Native.ElmFire.open + {-| Write a Json value to a Firebase location. @@ -282,17 +363,23 @@ The task may result in an error if the location is invalid or you have no permission to write this data. -} set : JE.Value -> Location -> Task Error Reference -set = Native.ElmFire.set False +set = + Native.ElmFire.set False + {-| Write a Json value to a Firebase location and specify a priority for that data. -} setWithPriority : JE.Value -> Priority -> Location -> Task Error Reference -setWithPriority = Native.ElmFire.setWithPriority False +setWithPriority = + Native.ElmFire.setWithPriority False + {-| Set a priority for the data at a Firebase location. -} setPriority : Priority -> Location -> Task Error Reference -setPriority = Native.ElmFire.setPriority +setPriority = + Native.ElmFire.setPriority + {-| Write the children in a Json value to a Firebase location. @@ -302,7 +389,9 @@ and will leave others untouched. It is also possible to do atomic multi-location updates as documented [here](https://www.firebase.com/blog/2015-09-24-atomic-writes-and-more.html). -} update : JE.Value -> Location -> Task Error Reference -update = Native.ElmFire.update False +update = + Native.ElmFire.update False + {-| Delete a Firebase location. @@ -312,7 +401,9 @@ The task may result in an error if the location is invalid or you have no permission to remove this data. -} remove : Location -> Task Error Reference -remove = Native.ElmFire.remove False +remove = + Native.ElmFire.remove False + {-| Transaction: Atomically modify the data at a location @@ -325,36 +416,48 @@ On success the task returns a tuple: Its first element indicates whether the transaction was commited (True) or aborted (False). Regardless, the second element is a Snapshot containing the resulting data at that location. -} -transaction : (Maybe JE.Value -> Action) - -> Location - -> Bool - -> Task Error (Bool, Snapshot) -transaction = Native.ElmFire.transaction +transaction : + (Maybe JE.Value -> Action) + -> Location + -> Bool + -> Task Error ( Bool, Snapshot ) +transaction = + Native.ElmFire.transaction + {-| Queue a `set` operation on the server that get executed as soon as the client disconnects. -} onDisconnectSet : JE.Value -> Location -> Task Error () -onDisconnectSet = Native.ElmFire.set True +onDisconnectSet = + Native.ElmFire.set True + {-| Queue a `setWithPriority` operation on the server that get executed as soon as the client disconnects. -} onDisconnectSetWithPriority : JE.Value -> Priority -> Location -> Task Error () -onDisconnectSetWithPriority = Native.ElmFire.setWithPriority True +onDisconnectSetWithPriority = + Native.ElmFire.setWithPriority True + {-| Queue a `update` operation on the server that get executed as soon as the client disconnects. -} onDisconnectUpdate : JE.Value -> Location -> Task Error () -onDisconnectUpdate = Native.ElmFire.update True +onDisconnectUpdate = + Native.ElmFire.update True + {-| Queue a `remove` operation on the server that get executed as soon as the client disconnects. -} onDisconnectRemove : Location -> Task Error () -onDisconnectRemove = Native.ElmFire.remove True +onDisconnectRemove = + Native.ElmFire.remove True + {-| Cancels all previously queued operations for this location and all children. -} onDisconnectCancel : Location -> Task Error () -onDisconnectCancel = Native.ElmFire.onDisconnectCancel +onDisconnectCancel = + Native.ElmFire.onDisconnectCancel {-| Query a Firebase location by subscription @@ -376,29 +479,40 @@ Additionally, this parameter may also specify ordering, filtering and limiting o The fourth parameter specifies the location to be queried. -} -subscribe : (Snapshot -> Task x a) - -> (Cancellation -> Task y b) - -> Query - -> Location - -> Task Error Subscription +subscribe : + (Snapshot -> Task x a) + -> (Cancellation -> Task y b) + -> Query + -> Location + -> Task Error Subscription subscribe createResponseTask = - subscribeConditional (Just << createResponseTask) + subscribeConditional (Just << createResponseTask) + + {- Query a Firebase location by subscription with optional reaction -Similar to `subscribe` except that the function given as the first parameter -can decide whether to run a task or not. + Similar to `subscribe` except that the function given as the first parameter + can decide whether to run a task or not. -} -subscribeConditional : (Snapshot -> Maybe (Task x a)) - -> (Cancellation -> Task y b) - -> Query - -> Location - -> Task Error Subscription -subscribeConditional = Native.ElmFire.subscribeConditional -{-| Cancel a query subscription -} + +subscribeConditional : + (Snapshot -> Maybe (Task x a)) + -> (Cancellation -> Task y b) + -> Query + -> Location + -> Task Error Subscription +subscribeConditional = + Native.ElmFire.subscribeConditional + + +{-| Cancel a query subscription +-} unsubscribe : Subscription -> Task Error () -unsubscribe = Native.ElmFire.unsubscribe +unsubscribe = + Native.ElmFire.unsubscribe + {-| Query a Firebase location for exactly one event of the specified type @@ -417,119 +531,182 @@ Additionally, this parameter may also specify ordering, filtering and limiting o The second parameter specifies the location to be queried. -} once : Query -> Location -> Task Error Snapshot -once = Native.ElmFire.once +once = + Native.ElmFire.once + -{-| A query specification: event type, possibly ordering with filtering and limiting -} +{-| A query specification: event type, possibly ordering with filtering and limiting +-} type Query - = ValueChanged OrderOptions - | ChildAdded OrderOptions - | ChildChanged OrderOptions - | ChildRemoved OrderOptions - | ChildMoved OrderOptions + = ValueChanged OrderOptions + | ChildAdded OrderOptions + | ChildChanged OrderOptions + | ChildRemoved OrderOptions + | ChildMoved OrderOptions + -{-| Build a query with event type "value changed" -} +{-| Build a query with event type "value changed" +-} valueChanged : OrderOptions -> Query -valueChanged = ValueChanged +valueChanged = + ValueChanged + -{-| Build a query with event type "child added" -} +{-| Build a query with event type "child added" +-} childAdded : OrderOptions -> Query -childAdded = ChildAdded +childAdded = + ChildAdded + -{-| Build a query with event type "child changed" -} +{-| Build a query with event type "child changed" +-} childChanged : OrderOptions -> Query -childChanged = ChildChanged +childChanged = + ChildChanged -{-| Build a query with event type "child removed" -} + +{-| Build a query with event type "child removed" +-} childRemoved : OrderOptions -> Query -childRemoved = ChildRemoved +childRemoved = + ChildRemoved -{-| Build a query with event type "child moved" -} + +{-| Build a query with event type "child moved" +-} childMoved : OrderOptions -> Query -childMoved = ChildMoved +childMoved = + ChildMoved -{-| Type to specify ordering, filtering and limiting of queries -} + +{-| Type to specify ordering, filtering and limiting of queries +-} type OrderOptions - = NoOrder - | OrderByChild String (RangeOptions JE.Value) LimitOptions - | OrderByValue (RangeOptions JE.Value) LimitOptions - | OrderByKey (RangeOptions String) LimitOptions - | OrderByPriority (RangeOptions (Priority, Maybe String)) LimitOptions + = NoOrder + | OrderByChild String (RangeOptions JE.Value) LimitOptions + | OrderByValue (RangeOptions JE.Value) LimitOptions + | OrderByKey (RangeOptions String) LimitOptions + | OrderByPriority (RangeOptions ( Priority, Maybe String )) LimitOptions -{-| Type to specify filtering options for the use within an ordered query -} + +{-| Type to specify filtering options for the use within an ordered query +-} type RangeOptions t - = NoRange - | StartAt t - | EndAt t - | Range t t - | EqualTo t + = NoRange + | StartAt t + | EndAt t + | Range t t + | EqualTo t -{-| Type to specify limiting the size of the query result set. Used within an ordered query -} + +{-| Type to specify limiting the size of the query result set. Used within an ordered query +-} type LimitOptions - = NoLimit - | LimitToFirst Int - | LimitToLast Int + = NoLimit + | LimitToFirst Int + | LimitToLast Int -{-| Don't order results -} + +{-| Don't order results +-} noOrder : OrderOptions -noOrder = NoOrder +noOrder = + NoOrder + {-| Order results by the value of a given child (or deep child, as documented [here](https://www.firebase.com/blog/2015-09-24-atomic-writes-and-more.html)) -} orderByChild : String -> RangeOptions JE.Value -> LimitOptions -> OrderOptions -orderByChild = OrderByChild +orderByChild = + OrderByChild + -{-| Order results by value -} +{-| Order results by value +-} orderByValue : RangeOptions JE.Value -> LimitOptions -> OrderOptions -orderByValue = OrderByValue +orderByValue = + OrderByValue -{-| Order results by key -} + +{-| Order results by key +-} orderByKey : RangeOptions String -> LimitOptions -> OrderOptions -orderByKey = OrderByKey +orderByKey = + OrderByKey -{-| Order results by priority (and maybe secondary by key) -} -orderByPriority : RangeOptions (Priority, Maybe String) -> LimitOptions -> OrderOptions -orderByPriority = OrderByPriority -{-| Don't filter the ordered results -} +{-| Order results by priority (and maybe secondary by key) +-} +orderByPriority : RangeOptions ( Priority, Maybe String ) -> LimitOptions -> OrderOptions +orderByPriority = + OrderByPriority + + +{-| Don't filter the ordered results +-} noRange : RangeOptions t -noRange = NoRange +noRange = + NoRange + {-| Filter the ordered results to start at a given value. -The type of the value depends on the order criterium -} +The type of the value depends on the order criterium +-} startAt : t -> RangeOptions t -startAt = StartAt +startAt = + StartAt + {-| Filter the ordered results to end at a given value. -The type of the value depends on the order criterium -} +The type of the value depends on the order criterium +-} endAt : t -> RangeOptions t -endAt = EndAt +endAt = + EndAt + {-| Filter the ordered results to start at a given value and to end at another value. -The type of the value depends on the order criterium -} +The type of the value depends on the order criterium +-} range : t -> t -> RangeOptions t -range = Range +range = + Range + {-| Filter the ordered results to equal a given value. -The type of the value depends on the order criterium -} +The type of the value depends on the order criterium +-} equalTo : t -> RangeOptions t -equalTo = EqualTo +equalTo = + EqualTo -{-| Don't limit the number of children in the result set of an ordered query -} + +{-| Don't limit the number of children in the result set of an ordered query +-} noLimit : LimitOptions -noLimit = NoLimit +noLimit = + NoLimit -{-| Limit the result set of an ordered query to the first certain number of children. -} + +{-| Limit the result set of an ordered query to the first certain number of children. +-} limitToFirst : Int -> LimitOptions -limitToFirst = LimitToFirst +limitToFirst = + LimitToFirst -{-| Limit the result set of an ordered query to the last certain number of children. -} + +{-| Limit the result set of an ordered query to the last certain number of children. +-} limitToLast : Int -> LimitOptions -limitToLast = LimitToLast +limitToLast = + LimitToLast + {-| Convert a snapshot's children into a list of snapshots @@ -538,19 +715,30 @@ So, if the snapshot results from a ordered valueChanged-query then toSnapshotList allows for conserving this ordering as a list. -} toSnapshotList : Snapshot -> List Snapshot -toSnapshotList = Native.ElmFire.toSnapshotList +toSnapshotList = + Native.ElmFire.toSnapshotList -{-| Convert a snapshot's children into a list of its values -} + +{-| Convert a snapshot's children into a list of its values +-} toValueList : Snapshot -> List JE.Value -toValueList = Native.ElmFire.toValueList +toValueList = + Native.ElmFire.toValueList -{-| Convert a snapshot's children into a list of its keys -} + +{-| Convert a snapshot's children into a list of its keys +-} toKeyList : Snapshot -> List String -toKeyList = Native.ElmFire.toKeyList +toKeyList = + Native.ElmFire.toKeyList + + +{-| Convert a snapshot's children into a list of key-value-pairs +-} +toPairList : Snapshot -> List ( String, JE.Value ) +toPairList = + Native.ElmFire.toPairList -{-| Convert a snapshot's children into a list of key-value-pairs -} -toPairList : Snapshot -> List (String, JE.Value) -toPairList = Native.ElmFire.toPairList {-| Exports the entire contents of a Snapshot as a JavaScript object. @@ -558,47 +746,71 @@ This is similar to .value except priority information is included (if available) making it suitable for backing up your data. -} exportValue : Snapshot -> JE.Value -exportValue = Native.ElmFire.exportValue +exportValue = + Native.ElmFire.exportValue + {-| Manually disconnect the client from the server -and disables automatic reconnection. -} +and disables automatic reconnection. +-} goOffline : Task x () -goOffline = Native.ElmFire.setOffline True +goOffline = + Native.ElmFire.setOffline True + {-| Manually reestablish a connection to the server -and enables automatic reconnection. -} -goOnline : Task x () -goOnline = Native.ElmFire.setOffline False - -{-| Subscribe to connection state changes -} -subscribeConnected : (Bool -> Task x a) - -> Location - -> Task Error Subscription +and enables automatic reconnection. +-} +goOnline : Task x () +goOnline = + Native.ElmFire.setOffline False + + +{-| Subscribe to connection state changes +-} +subscribeConnected : + (Bool -> Task x a) + -> Location + -> Task Error Subscription subscribeConnected createResponseTask location = - subscribeConditional - ( \snapshot -> case JD.decodeValue JD.bool snapshot.value of - Ok state -> Just (createResponseTask state) - Err _ -> Nothing - ) - (always (Task.succeed ())) - (valueChanged noOrder) - (location |> root |> sub ".info/connected") - -{-| Subscribe to server time offset -} -subscribeServerTimeOffset : (Time -> Task x a) - -> Location - -> Task Error Subscription + subscribeConditional + (\snapshot -> + case JD.decodeValue JD.bool snapshot.value of + Ok state -> + Just (createResponseTask state) + + Err _ -> + Nothing + ) + (always (Task.succeed ())) + (valueChanged noOrder) + (location |> root |> sub ".info/connected") + + +{-| Subscribe to server time offset +-} +subscribeServerTimeOffset : + (Time -> Task x a) + -> Location + -> Task Error Subscription subscribeServerTimeOffset createResponseTask location = - subscribeConditional - ( \snapshot -> case JD.decodeValue JD.float snapshot.value of - Ok offset -> Just (createResponseTask (offset * Time.millisecond)) - Err _ -> Nothing - ) - (always (Task.succeed ())) - (valueChanged noOrder) - (location |> root |> sub ".info/serverTimeOffset") + subscribeConditional + (\snapshot -> + case JD.decodeValue JD.float snapshot.value of + Ok offset -> + Just (createResponseTask (offset * Time.millisecond)) + + Err _ -> + Nothing + ) + (always (Task.succeed ())) + (valueChanged noOrder) + (location |> root |> sub ".info/serverTimeOffset") + {-| A placeholder value for auto-populating the current timestamp -(time since the Unix epoch, in milliseconds) by the Firebase servers -} +(time since the Unix epoch, in milliseconds) by the Firebase servers +-} serverTimeStamp : JE.Value -serverTimeStamp = Native.ElmFire.serverTimeStamp +serverTimeStamp = + Native.ElmFire.serverTimeStamp diff --git a/test/src/TaskTest.elm b/test/src/TaskTest.elm index 8638fad..f8b3b3f 100644 --- a/test/src/TaskTest.elm +++ b/test/src/TaskTest.elm @@ -1,16 +1,29 @@ -module TaskTest exposing - ( testDisplay - , runTest - , test, sequence - , succeeds, fails, equals, meets, errorMeets - , clear, createReporter, map, printMapResult, printResult, printString - , (|>>), (|>+), (|>-) - ) +module TaskTest + exposing + ( testDisplay + , runTest + , test + , sequence + , succeeds + , fails + , equals + , meets + , errorMeets + , clear + , createReporter + , map + , printMapResult + , printResult + , printString + , (|>>) + , (|>+) + , (|>-) + ) {- A Sketch of a testing framework for task-based code. -This is work in progress. -We aim to make the logging output look much more nicer. + This is work in progress. + We aim to make the logging output look much more nicer. -} import Signal exposing (Signal, Mailbox, mailbox) @@ -19,227 +32,315 @@ import Task exposing (Task, andThen, onError, fail, succeed) import Html exposing (Html, div, span, text, a, h1, h2) import Html.Attributes exposing (href, target, class) + ------------------------------------------------------------------------------- -type Report = Line String Activity String -type Activity = RunSequence | RunTask | TestPass | TestError - | TestPrint | TestPrintPre | RunReporter -reports: Mailbox (Maybe Report) -reports = mailbox Nothing +type Report + = Line String Activity String + + +type Activity + = RunSequence + | RunTask + | TestPass + | TestError + | TestPrint + | TestPrintPre + | RunReporter + + +reports : Mailbox (Maybe Report) +reports = + mailbox Nothing + report : String -> Activity -> String -> Task x () report context activity text = - Signal.send reports.address <| - Just (Line context activity text) + Signal.send reports.address <| + Just (Line context activity text) + + +type alias Model = + Dict String (List Report) -type alias Model = Dict String (List Report) startModel : Model -startModel = Dict.empty +startModel = + Dict.empty + progression : Maybe Report -> Model -> Model progression maybeReport model = - case maybeReport of - Nothing -> model - Just report -> - case report of - Line context activity text -> - Dict.update - context - ( \step -> case step of - Nothing -> Just [report] - Just prevReports -> Just (report :: prevReports) - ) + case maybeReport of + Nothing -> model + Just report -> + case report of + Line context activity text -> + Dict.update + context + (\step -> + case step of + Nothing -> + Just [ report ] + + Just prevReports -> + Just (report :: prevReports) + ) + model + + state : Signal Model -state = Signal.foldp progression startModel reports.signal +state = + Signal.foldp progression startModel reports.signal + testDisplay : Signal Html -testDisplay = Signal.map view state +testDisplay = + Signal.map view state + + ------------------------------------------------------------------------------- + view : Model -> Html view model = - div [class "report"] <| - List.map - viewStep - (Dict.values model) + div [ class "report" ] <| + List.map + viewStep + (Dict.values model) + viewStep : List Report -> Html viewStep reports = - div [class "step"] - ( List.foldl -- reverses the list for display - ( \report htmlList -> viewReport report :: htmlList ) - [] - reports - ) + div [ class "step" ] + (List.foldl + -- reverses the list for display + (\report htmlList -> viewReport report :: htmlList) + [] + reports + ) + viewReport : Report -> Html viewReport (Line context activity txt) = - div - [ class ( - case activity of - RunSequence -> "header-sequence" - RunTask -> "header-task" - RunReporter -> "header-reporter" - TestPass -> "test pass" - TestError -> "test error" - TestPrint -> "test print" - TestPrintPre -> "test print pre" - ) - ] - [text txt] + div + [ class + (case activity of + RunSequence -> + "header-sequence" + + RunTask -> + "header-task" + + RunReporter -> + "header-reporter" + + TestPass -> + "test pass" + + TestError -> + "test error" + + TestPrint -> + "test print" + + TestPrintPre -> + "test print pre" + ) + ] + [ text txt ] + + ------------------------------------------------------------------------------- -type alias Context = String -type alias TestTask x a = Context -> (Task x a) + +type alias Context = + String + + +type alias TestTask x a = + Context -> Task x a + runTest : TestTask x a -> Task x a -runTest testTask = testTask "no test name" +runTest testTask = + testTask "no test name" + test : String -> Task x a -> TestTask x a test description task = - \context -> - report context RunTask description - `andThen` - \_ -> task + \context -> + report context RunTask description + `andThen` \_ -> task + -createReporter: String -> TestTask y (b -> Task z ()) +createReporter : String -> TestTask y (b -> Task z ()) createReporter description = - \context -> - report context RunReporter description - `andThen` - \_ -> succeed ( \val -> report context TestPrint (toString val) ) + \context -> + report context RunReporter description + `andThen` \_ -> succeed (\val -> report context TestPrint (toString val)) -clear: TestTask y () + +clear : TestTask y () clear = - \context -> succeed () + \context -> succeed () + succeeds : TestTask x a -> TestTask x a succeeds testTask = - \context -> - ( testTask context - `onError` \err -> - ( report context TestError "task failed" - `andThen` \_ -> fail err + \context -> + (testTask context + `onError` + \err -> + (report context TestError "task failed" + `andThen` \_ -> fail err + ) ) - ) - `andThen` \val -> - ( report context TestPass "task succeeds" - `andThen` \_ -> succeed val - ) + `andThen` + \val -> + (report context TestPass "task succeeds" + `andThen` \_ -> succeed val + ) + -fails: TestTask x a -> TestTask x a +fails : TestTask x a -> TestTask x a fails testTask = - \context -> - ( testTask context - `onError` \err -> - ( report context TestPass "task failed as expected" - `andThen` \_ -> fail err + \context -> + (testTask context + `onError` + \err -> + (report context TestPass "task failed as expected" + `andThen` \_ -> fail err + ) ) - ) - `andThen` \val -> - ( report context TestError "task succeeds unexpectedly" - `andThen` \_ -> succeed val - ) + `andThen` + \val -> + (report context TestError "task succeeds unexpectedly" + `andThen` \_ -> succeed val + ) + equals : String -> a -> TestTask x a -> TestTask x a equals description expectedValue = - meets description ((==) expectedValue) + meets description ((==) expectedValue) + meets : String -> (a -> Bool) -> TestTask x a -> TestTask x a meets description condition testTask = - \context -> - ( testTask context - `onError` \err -> - ( report context TestError (description ++ " [task failed]") - `andThen` \_ -> fail err - ) - ) - `andThen` \val -> - ( ( if condition val - then report context TestPass description - else report context TestError (description ++ " [unfulfilled]") + \context -> + (testTask context + `onError` + \err -> + (report context TestError (description ++ " [task failed]") + `andThen` \_ -> fail err + ) ) - `andThen` \_ -> succeed val - ) + `andThen` + \val -> + ((if condition val then + report context TestPass description + else + report context TestError (description ++ " [unfulfilled]") + ) + `andThen` \_ -> succeed val + ) + errorMeets : String -> (x -> Bool) -> TestTask x a -> TestTask x a errorMeets description condition testTask = - \context -> - ( testTask context - `onError` \err -> - ( ( if condition err - then report context TestPass description - else report context TestError (description ++ " [unfulfilled]") - ) - `andThen` \_ -> fail err + \context -> + (testTask context + `onError` + \err -> + ((if condition err then + report context TestPass description + else + report context TestError (description ++ " [unfulfilled]") + ) + `andThen` \_ -> fail err + ) ) - ) - `andThen` \val -> - ( report context TestError (description ++ " [task succeeds unexpectedly]") - `andThen` \_ -> succeed val - ) + `andThen` + \val -> + (report context TestError (description ++ " [task succeeds unexpectedly]") + `andThen` \_ -> succeed val + ) + printMapResult : (a -> b) -> TestTask x a -> TestTask x a printMapResult mapping testTask = - \context -> - ( testTask context - `onError` \err -> - ( report context TestPrint ("Error: " ++ toString err) - `andThen` \_ -> fail err + \context -> + (testTask context + `onError` + \err -> + (report context TestPrint ("Error: " ++ toString err) + `andThen` \_ -> fail err + ) ) - ) - `andThen` \val -> - ( report context TestPrint ("Result: " ++ toString (mapping val)) - `andThen` \_ -> succeed val - ) + `andThen` + \val -> + (report context TestPrint ("Result: " ++ toString (mapping val)) + `andThen` \_ -> succeed val + ) + printResult : TestTask x a -> TestTask x a printResult testTask = - printMapResult identity testTask + printMapResult identity testTask + printString : TestTask x String -> TestTask x String printString testTask = - \context -> - testTask context - `andThen` \str -> - ( report context TestPrintPre str - `andThen` \_ -> succeed str - ) + \context -> + testTask context + `andThen` + \str -> + (report context TestPrintPre str + `andThen` \_ -> succeed str + ) + map : (a -> b) -> TestTask x a -> TestTask x b map func testTask = - \context -> - Task.map func (testTask context) + \context -> + Task.map func (testTask context) + infixl 1 |>> (|>>) : TestTask x a -> (TestTask x a -> TestTask x b) -> TestTask x b -(|>>) = (|>) +(|>>) = + (|>) + infixl 0 |>- (|>-) : TestTask x a -> TestTask y b -> TestTask y b (|>-) testTask1 task2 = - \context -> - Task.toMaybe (testTask1 (context ++ "-1")) - `andThen` \_ -> (task2 (context ++ "-2")) + \context -> + Task.toMaybe (testTask1 (context ++ "-1")) + `andThen` \_ -> (task2 (context ++ "-2")) + infixl 0 |>+ (|>+) : TestTask x a -> (a -> TestTask x b) -> TestTask x b (|>+) testTask1 callback2 = - \context -> - testTask1 (context ++ "+1") - `andThen` \res1 -> - let testTask2 = callback2 res1 in - testTask2 (context ++ "+2") + \context -> + testTask1 (context ++ "+1") + `andThen` + \res1 -> + let + testTask2 = + callback2 res1 + in + testTask2 (context ++ "+2") + sequence : String -> TestTask x a -> TestTask x a sequence name testTask = - \context -> - report name RunSequence name - `andThen` - \_ -> testTask name + \context -> + report name RunSequence name + `andThen` \_ -> testTask name diff --git a/test/src/Tests.elm b/test/src/Tests.elm index dea7ce9..00dadd1 100644 --- a/test/src/Tests.elm +++ b/test/src/Tests.elm @@ -1,10 +1,13 @@ {- A Sketch of a Test App for ElmFire -A given sequence of tasks is run on the Firebase API. + A given sequence of tasks is run on the Firebase API. -This is work in progress. + This is work in progress. -} + +module Main exposing (..) + import String import List import Time @@ -13,57 +16,79 @@ import Json.Encode as JE import Json.Decode as JD exposing ((:=)) import Html exposing (Html, div, span, text, a, h1, h2) import Html.Attributes exposing (href, target, class) - import TaskTest exposing (..) - import ElmFire exposing (..) import ElmFire.Auth as Auth -------------------------------------------------------------------------------- +------------------------------------------------------------------------------- -- Use this test Firebase. The tests below rely on some settings in this Firebase. -- Individual executions of this test suite use independent branches in this Firebase. + + url : String -url = "https://elmfiretest.firebaseio.com/" +url = + "https://elmfiretest.firebaseio.com/" + dino : Location -dino = fromUrl url |> sub "dinosaur-facts" +dino = + fromUrl url |> sub "dinosaur-facts" + + ------------------------------------------------------------------------------- + isNothing : Maybe a -> Bool -isNothing x = case x of - Just _ -> False - Nothing -> True +isNothing x = + case x of + Just _ -> + False + + Nothing -> + True + isJust : Maybe a -> Bool -isJust = not << isNothing +isJust = + not << isNothing + isLocationError : Error -> Bool isLocationError err = - err.tag == LocationError + err.tag == LocationError + isPermissionError : Error -> Bool isPermissionError err = - err.tag == PermissionError + err.tag == PermissionError + action1 : Maybe JE.Value -> Action action1 maybeValue = - case maybeValue of - Just value -> - case JD.decodeValue JD.string value of - Ok str -> Set (JE.string <| str ++ "!") - _ -> Remove - _ -> Abort + case maybeValue of + Just value -> + case JD.decodeValue JD.string value of + Ok str -> + Set (JE.string <| str ++ "!") + + _ -> + Remove + + _ -> + Abort + type alias PrintableSnapshot = - { subscription: Subscription - , key: String - , existing: Bool - , value: JE.Value - , prevKey: Maybe String - , priority: Priority - } + { subscription : Subscription + , key : String + , existing : Bool + , value : JE.Value + , prevKey : Maybe String + , priority : Priority + } + printableSnapshot : Snapshot -> PrintableSnapshot printableSnapshot snap = @@ -75,386 +100,356 @@ printableSnapshot snap = , priority = snap.priority } + type Response - = NoResponse - | Data PrintableSnapshot - | Canceled Cancellation + = NoResponse + | Data PrintableSnapshot + | Canceled Cancellation + test1 = + -- Start tests by opening a path and creating a reference for it ------------ + sequence "Test Sequence" + (test "open" (open (fromUrl url |> sub "test" |> push |> push)) + |>> succeeds + |>> meets "url of opened ref starts with base-url" (\ref -> url `String.startsWith` toUrl ref) + |>+ \ref -> + -- Connection state tests --------------------------------------------------- + createReporter "Metadata results" + |>+ \reporterConnected -> + test "subscribe to connection state changes" + (subscribeConnected + ((\state -> "connected: " ++ toString state) >> reporterConnected) + (location ref) + ) + |>> succeeds + |>- test "subscribe to serverTimeOffset changes" + (subscribeServerTimeOffset + ((\offset -> "serverTimeOffset: " ++ toString offset) >> reporterConnected) + (location ref) + ) + |>> succeeds + -- Create a new path in the Firebase for this test run ---------------------- + |>- + test "setWithPriority" (setWithPriority (JE.string "Hello") (NumberPriority 42) (location ref)) + |>> meets "set returned same ref" (\refReturned -> toUrl refReturned == toUrl ref) + |>> map location + |>+ \loc -> + test "once valueChanged (at child)" (once (valueChanged noOrder) loc) + |>> printMapResult printableSnapshot + |>> meets "once returned same key" (\snapshot -> snapshot.key == key ref) + |>- test "onDisconnectSet" + (onDisconnectSet (JE.string "disconnected") (loc |> parent |> sub "onlineState")) + |>> printResult + |>> succeeds + |>- test "onDisconnectSet has not written yet" + (once (valueChanged noOrder) (loc |> parent |> sub "onlineState")) + |>> meets "value is not existing" + (\snapshot -> not snapshot.existing) + |>> meets "value is null" + (\snapshot -> snapshot.value == JE.null) + |>- test "go offline" goOffline + |>> succeeds + |>- test "go online" goOnline + |>> succeeds + |>- test "onDisconnectSet has now written the value" + (once (valueChanged noOrder) (loc |> parent |> sub "onlineState")) + |>> meets "value is written" + (\snapshot -> snapshot.value == JE.string "disconnected") + -- Test updating ------------------------------------------------------------ + |>- + test "deep update" + (update + (JE.object [ ( "a/b", JE.string "AB" ) ]) + (loc |> parent) + `Task.andThen` + \_ -> + once + (valueChanged noOrder) + (loc |> parent |> sub "a/b") + ) + |>> meets "updated value ok" + (\snapshot -> snapshot.value == JE.string "AB") + -- User management tests ---------------------------------------------------- + |>- + test "generate a test email address from the resulting key string" + (Task.succeed <| (key ref) ++ "@b.com") + |>> printResult + |>+ \email -> + test "create user" (Auth.userOperation loc (Auth.createUser email "pw1")) + |>> printResult + |>> meets "returns a uid" isJust + |>- test "change password with wrong old password" (Auth.userOperation loc (Auth.changePassword email "wrong" "pw2")) + |>> errorMeets "reports AuthError InvalidPassword" + (\err -> err.tag == AuthError InvalidPassword) + |>- test "change password" (Auth.userOperation loc (Auth.changePassword email "pw1" "pw2")) + |>> printResult + |>> succeeds + |>- test "change email" (Auth.userOperation loc (Auth.changeEmail email "pw2" ("2" ++ email))) + |>> succeeds + {- Don't run this test by default (Firebase sends an nonaddressable email each time) + |>- test "reset password" (Auth.userOperation loc (Auth.resetPassword ("2" ++ email))) + |>> succeeds + -} + |>- + test "remove nonexistent user" (Auth.userOperation loc (Auth.removeUser (email) "pw2")) + |>> errorMeets "reports AuthError InvalidUser" + (\err -> err.tag == AuthError InvalidUser) + |>- test "remove user" (Auth.userOperation loc (Auth.removeUser ("2" ++ email) "pw2")) + |>> succeeds + -- Authentication tests ----------------------------------------------------- + |>- + createReporter "authSubscription results" + |>+ \reporterAuth -> + test "subscribe to authentication changes" + (Auth.subscribeAuth + reporterAuth + loc + ) + |>> succeeds + |>- test "unauthenticate" (Auth.unauthenticate loc) + |>> printResult + |>> succeeds + |>- test "getAuth while not authenticated" (Auth.getAuth loc) + |>> printResult + |>> equals "getAuth returns Nothing" Nothing + |>- test "getAuth with invalid location" (Auth.getAuth (loc |> root |> parent)) + |>> errorMeets "reports LocationError" isLocationError + |>- test "auth anonymously" + (Auth.authenticate loc [ Auth.rememberNone ] Auth.asAnonymous) + |>> printResult + |>- test "getAuth after authentication" (Auth.getAuth loc) + |>> printResult + |>> meets "getAuth returns an anonymous provider" + (\maybeAuth -> + case maybeAuth of + Just auth -> + auth.provider == "anonymous" + + _ -> + False + ) + |>- test "re-auth with wrong password" + (Auth.unauthenticate loc + `Task.andThen` + \_ -> + Auth.authenticate loc [ Auth.rememberNone ] (Auth.withPassword "a@b.com" "bad") + ) + |>> errorMeets "reports AuthError InvalidPassword" + (\err -> err.tag == AuthError InvalidPassword) + |>- test "re-auth with right password" + (Auth.unauthenticate loc + `Task.andThen` + \_ -> + Auth.authenticate loc [ Auth.rememberNone ] (Auth.withPassword "a@b.com" "good") + ) + |>> printResult + |>> meets "provider-specifics contain the given email address" + (\auth -> JD.decodeValue ("email" := JD.string) auth.specifics == Ok "a@b.com") + -- Test reading and writing (except complex queries) ------------------------ + |>- + test "once valueChanged (at child)" (once (valueChanged noOrder) loc) + |>> printMapResult printableSnapshot + |>> meets "once returned same key" (\snapshot -> snapshot.key == key ref) + |>> meets "once returned right value" (\snapshot -> snapshot.value == JE.string "Hello") + |>> meets "once returned right prevKey" (\snapshot -> snapshot.prevKey == Nothing) + |>> meets "once returned right priority" (\snapshot -> snapshot.priority == NumberPriority 42) + |>> map exportValue + |>> meets "export contains right value" + (\ex -> JD.decodeValue (".value" := JD.string) ex == Ok "Hello") + |>> meets "export contains right priority" + (\ex -> JD.decodeValue (".priority" := JD.int) ex == Ok 42) + |>- createReporter "subscription results" + |>+ \reporter1 -> + test "subscribe child added (at parent)" + (subscribe + (printableSnapshot >> Data >> reporter1) + (Canceled >> reporter1) + (childAdded noOrder) + (parent loc) + ) + |>> succeeds + |>> printResult + |>- test "sleep 1s" (Task.sleep (1 * Time.second)) + |>- test "set child with serverTimeStamp" (set serverTimeStamp (loc |> parent |> sub "server timestamp")) + |>- test "set another child" (set (JE.string "Elmers") (loc |> parent |> push)) + |>> map key + |>> printResult + |>+ \key -> + test "transaction on that child" + (transaction action1 (loc |> parent |> sub key) True) + |>> meets "committed and returned changed value" + (\( committed, snapshot ) -> + committed && snapshot.value == JE.string "Elmers!" + ) + |>> printMapResult (snd >> printableSnapshot) + |>- test "once valueChanged at non-existing location" + (once (valueChanged noOrder) (sub "_non_existing_key_" loc)) + |>> meets "returns non-existing" (\snapshot -> not snapshot.existing) + |>- test "set without permission" + (set (JE.null) (fromUrl url |> sub "unaccessible")) + |>> printResult + |>> fails + |>> errorMeets "reports PermissionError" isPermissionError + |>- clear + |>- test "once without permission" + (once (valueChanged noOrder) (fromUrl url |> sub "unaccessible")) + |>> printResult + |>> fails + |>> errorMeets "reports PermissionError" isPermissionError + |>- clear + |>- createReporter "subscription without permission results" + |>+ \reporter2 -> + test "subscribe without permission" + (subscribe + (printableSnapshot >> Data >> reporter2) + (Canceled >> reporter2) + (valueChanged noOrder) + (fromUrl url |> sub "unaccessible") + ) + |>> printResult + |>- clear + |>- test "transaction without permission" + (transaction action1 (fromUrl url |> sub "unaccessible") True) + |>> meets "not committed" (\( committed, _ ) -> not committed) + |>- clear + |>- test "open root's parent" (open (fromUrl url |> root |> parent)) + |>> printResult + |>> errorMeets "reports LocationError" isLocationError + |>- clear + |>- test "open an invalid URL" (open (fromUrl "not-a-url")) + |>> printResult + |>> errorMeets "reports LocationError" isLocationError + |>- clear + |>- test "subscribe with invalid URL" + (subscribe + (always Task.succeed ()) + (always Task.succeed ()) + (valueChanged noOrder) + (fromUrl "not-a-url") + ) + |>> printResult + |>> fails + |>> errorMeets "reports LocationError" isLocationError + |>- test "transaction with invalid URL" + (transaction action1 (fromUrl "not-a-url") True) + |>> printResult + |>> fails + |>> errorMeets "reports LocationError" isLocationError + |>- clear + -- Test complex queries, using the dino example data from Firebase docs ----- + |>- + test "dino test data" (once (valueChanged noOrder) dino) + |>> map (.value >> JE.encode 2) + |>> printString + |>- test "toSnapshotList" (once (valueChanged noOrder) (dino |> sub "scores")) + |>> map (toSnapshotList >> List.map printableSnapshot) + |>> printResult + |>- test "dinos, ordered by child 'height', limited to last 2" + (once + (valueChanged (orderByChild "height" noRange (limitToLast 2))) + (dino |> sub "dinosaurs") + ) + |>> map (toValueList >> JE.list >> JE.encode 2) + |>> printString + |>- createReporter "subscription results: dino scores, ordered by value, limited to first 3" + |>+ \reporterDino -> + test "subscribe dino scores, ordered by value, limited to first 3" + (subscribe + (printableSnapshot >> Data >> reporterDino) + (Canceled >> reporterDino) + (childAdded (orderByValue noRange (limitToFirst 3))) + (dino |> sub "scores") + ) + |>> printResult + |>- test "dinos, ordered by key, limited to first 2" + (once + (valueChanged (orderByKey noRange (limitToFirst 2))) + (dino |> sub "dinosaurs") + ) + |>> map (toKeyList >> String.join " ") + |>> printString + |>- test "order by priority" + (once (valueChanged (orderByPriority noRange noLimit)) (parent loc)) + |>> map (toSnapshotList >> List.map .priority) + |>> printResult + |>- test "order by priority, start at priority number 10" + (once + (valueChanged + (orderByPriority + (startAt ( NumberPriority 10, Nothing )) + noLimit + ) + ) + (parent loc) + ) + |>> map (toSnapshotList >> List.map .priority) + |>> printResult + |>- test "order by priority, end at priority number 10" + (once + (valueChanged + (orderByPriority + (endAt ( NumberPriority 10, Nothing )) + noLimit + ) + ) + (parent loc) + ) + |>> map (toSnapshotList >> List.map .priority) + |>> printResult + |>- test "order by child 'height', start at value 3, end at value 10" + (once + (valueChanged + (orderByChild "height" + (range (JE.int 3) (JE.int 10)) + noLimit + ) + ) + (dino |> sub "dinosaurs") + ) + |>> map (toPairList >> JE.object >> JE.encode 2) + |>> printString + |>- test "dinos, ordered by key, starting with letter 'l'" + (once + (valueChanged + (orderByKey (range "l" "l~") noLimit) + ) + (dino |> sub "dinosaurs") + ) + |>> map (toKeyList >> String.join " ") + |>> printString + |>- test "dinos, ordered by prioriy, start at NoPriority and key 's'" + (once + (valueChanged + (orderByPriority + (startAt ( NoPriority, (Just "s") )) + noLimit + ) + ) + (dino |> sub "dinosaurs") + ) + |>> map (toKeyList >> String.join " ") + |>> printString + |>- test "end of test sequence" (Task.succeed ()) + |>- clear + ) - -- Start tests by opening a path and creating a reference for it ------------ - - sequence "Test Sequence" ( - - test "open" (open (fromUrl url |> sub "test" |> push |> push)) - |>> succeeds - |>> meets "url of opened ref starts with base-url" (\ref -> url `String.startsWith` toUrl ref ) - - |>+ \ref - - -- Connection state tests --------------------------------------------------- - - -> createReporter "Metadata results" - |>+ \reporterConnected - -> test "subscribe to connection state changes" - ( subscribeConnected - ((\state -> "connected: " ++ toString state) >> reporterConnected) - (location ref) - ) - |>> succeeds - |>- test "subscribe to serverTimeOffset changes" - ( subscribeServerTimeOffset - ((\offset -> "serverTimeOffset: " ++ toString offset) >> reporterConnected) - (location ref) - ) - |>> succeeds - - -- Create a new path in the Firebase for this test run ---------------------- - - - |>- test "setWithPriority" (setWithPriority (JE.string "Hello") (NumberPriority 42) (location ref)) - |>> meets "set returned same ref" (\refReturned -> toUrl refReturned == toUrl ref) - |>> map location - |>+ \loc - -> test "once valueChanged (at child)" (once (valueChanged noOrder) loc) - |>> printMapResult printableSnapshot - |>> meets "once returned same key" (\snapshot -> snapshot.key == key ref) - - |>- test "onDisconnectSet" - (onDisconnectSet (JE.string "disconnected") (loc |> parent |> sub "onlineState")) - |>> printResult - |>> succeeds - |>- test "onDisconnectSet has not written yet" - (once (valueChanged noOrder) (loc |> parent |> sub "onlineState")) - |>> meets "value is not existing" - (\snapshot -> not snapshot.existing) - |>> meets "value is null" - (\snapshot -> snapshot.value == JE.null) - - |>- test "go offline" goOffline - |>> succeeds - |>- test "go online" goOnline - |>> succeeds - - |>- test "onDisconnectSet has now written the value" - (once (valueChanged noOrder) (loc |> parent |> sub "onlineState")) - |>> meets "value is written" - (\snapshot -> snapshot.value == JE.string "disconnected") - - -- Test updating ------------------------------------------------------------ - - |>- test "deep update" - ( update - (JE.object [("a/b", JE.string "AB")]) - (loc |> parent) - `Task.andThen` \_ -> - once - (valueChanged noOrder) - (loc |> parent |> sub "a/b") - ) - |>> meets "updated value ok" - (\snapshot -> snapshot.value == JE.string "AB") - - -- User management tests ---------------------------------------------------- - - |>- test "generate a test email address from the resulting key string" - (Task.succeed <| (key ref) ++ "@b.com") - |>> printResult - |>+ \email - -> test "create user" (Auth.userOperation loc (Auth.createUser email "pw1")) - |>> printResult - |>> meets "returns a uid" isJust - - |>- test "change password with wrong old password" (Auth.userOperation loc (Auth.changePassword email "wrong" "pw2")) - |>> errorMeets "reports AuthError InvalidPassword" - (\err -> err.tag == AuthError InvalidPassword) - - |>- test "change password" (Auth.userOperation loc (Auth.changePassword email "pw1" "pw2")) - |>> printResult - |>> succeeds - - |>- test "change email" (Auth.userOperation loc (Auth.changeEmail email "pw2" ("2" ++ email))) - |>> succeeds - - {- Don't run this test by default (Firebase sends an nonaddressable email each time) - |>- test "reset password" (Auth.userOperation loc (Auth.resetPassword ("2" ++ email))) - |>> succeeds - -} - - |>- test "remove nonexistent user" (Auth.userOperation loc (Auth.removeUser (email) "pw2")) - |>> errorMeets "reports AuthError InvalidUser" - (\err -> err.tag == AuthError InvalidUser) - - |>- test "remove user" (Auth.userOperation loc (Auth.removeUser ("2" ++ email) "pw2")) - |>> succeeds - - -- Authentication tests ----------------------------------------------------- - - |>- createReporter "authSubscription results" - |>+ \reporterAuth - -> test "subscribe to authentication changes" - ( Auth.subscribeAuth - reporterAuth - loc - ) - |>> succeeds - - |>- test "unauthenticate" (Auth.unauthenticate loc) - |>> printResult - |>> succeeds - - |>- test "getAuth while not authenticated" (Auth.getAuth loc) - |>> printResult - |>> equals "getAuth returns Nothing" Nothing - - |>- test "getAuth with invalid location" (Auth.getAuth (loc |> root |> parent)) - |>> errorMeets "reports LocationError" isLocationError - - |>- test "auth anonymously" - (Auth.authenticate loc [Auth.rememberNone] Auth.asAnonymous) - |>> printResult - - |>- test "getAuth after authentication" (Auth.getAuth loc) - |>> printResult - |>> meets "getAuth returns an anonymous provider" - (\maybeAuth -> case maybeAuth of - Just auth -> auth.provider == "anonymous" - _ -> False - ) - - |>- test "re-auth with wrong password" (Auth.unauthenticate loc `Task.andThen` \_ -> - Auth.authenticate loc [Auth.rememberNone] (Auth.withPassword "a@b.com" "bad")) - |>> errorMeets "reports AuthError InvalidPassword" - (\err -> err.tag == AuthError InvalidPassword) - - |>- test "re-auth with right password" (Auth.unauthenticate loc `Task.andThen` \_ -> - Auth.authenticate loc [Auth.rememberNone] (Auth.withPassword "a@b.com" "good")) - |>> printResult - |>> meets "provider-specifics contain the given email address" - (\auth -> JD.decodeValue ("email" := JD.string) auth.specifics == Ok "a@b.com") - - -- Test reading and writing (except complex queries) ------------------------ - - |>- test "once valueChanged (at child)" (once (valueChanged noOrder) loc) - |>> printMapResult printableSnapshot - |>> meets "once returned same key" (\snapshot -> snapshot.key == key ref) - |>> meets "once returned right value" (\snapshot -> snapshot.value == JE.string "Hello") - |>> meets "once returned right prevKey" (\snapshot -> snapshot.prevKey == Nothing) - |>> meets "once returned right priority" (\snapshot -> snapshot.priority == NumberPriority 42) - |>> map exportValue - |>> meets "export contains right value" - (\ex -> JD.decodeValue (".value" := JD.string) ex == Ok "Hello") - |>> meets "export contains right priority" - (\ex -> JD.decodeValue (".priority" := JD.int) ex == Ok 42) - - |>- createReporter "subscription results" - |>+ \reporter1 - -> test "subscribe child added (at parent)" - ( subscribe - (printableSnapshot >> Data >> reporter1) - (Canceled >> reporter1) - (childAdded noOrder) - (parent loc) - ) - |>> succeeds - |>> printResult - - |>- test "sleep 1s" ( Task.sleep (1 * Time.second) ) - |>- test "set child with serverTimeStamp" ( set serverTimeStamp (loc |> parent |> sub "server timestamp") ) - |>- test "set another child" ( set (JE.string "Elmers") (loc |> parent |> push) ) - |>> map key - |>> printResult - - |>+ \key - -> test "transaction on that child" - (transaction action1 (loc |> parent |> sub key) True) - |>> meets "committed and returned changed value" - (\(committed, snapshot) -> - committed && snapshot.value == JE.string "Elmers!" - ) - |>> printMapResult (snd >> printableSnapshot) - - |>- test "once valueChanged at non-existing location" - (once (valueChanged noOrder) (sub "_non_existing_key_" loc)) - |>> meets "returns non-existing" (\snapshot -> not snapshot.existing) - - |>- test "set without permission" - ( set (JE.null) (fromUrl url |> sub "unaccessible") ) - |>> printResult - |>> fails - |>> errorMeets "reports PermissionError" isPermissionError - |>- clear - - |>- test "once without permission" - ( once (valueChanged noOrder) (fromUrl url |> sub "unaccessible") ) - |>> printResult - |>> fails - |>> errorMeets "reports PermissionError" isPermissionError - |>- clear - - |>- createReporter "subscription without permission results" - |>+ \reporter2 - -> test "subscribe without permission" - ( subscribe - (printableSnapshot >> Data >> reporter2) - (Canceled >> reporter2) - (valueChanged noOrder) - (fromUrl url |> sub "unaccessible") - ) - |>> printResult - |>- clear - - |>- test "transaction without permission" - (transaction action1 (fromUrl url |> sub "unaccessible") True) - |>> meets "not committed" (\(committed, _) -> not committed) - |>- clear - - |>- test "open root's parent" ( open (fromUrl url |> root |> parent) ) - |>> printResult - |>> errorMeets "reports LocationError" isLocationError - |>- clear - - |>- test "open an invalid URL" ( open (fromUrl "not-a-url") ) - |>> printResult - |>> errorMeets "reports LocationError" isLocationError - |>- clear - - |>- test "subscribe with invalid URL" - ( subscribe - (always Task.succeed ()) (always Task.succeed ()) - (valueChanged noOrder) (fromUrl "not-a-url") - ) - |>> printResult - |>> fails - |>> errorMeets "reports LocationError" isLocationError - - |>- test "transaction with invalid URL" - (transaction action1 (fromUrl "not-a-url") True) - |>> printResult - |>> fails - |>> errorMeets "reports LocationError" isLocationError - |>- clear - - -- Test complex queries, using the dino example data from Firebase docs ----- - - |>- test "dino test data" (once (valueChanged noOrder) dino) - |>> map (.value >> JE.encode 2) - |>> printString - - |>- test "toSnapshotList" (once (valueChanged noOrder) (dino |> sub "scores")) - |>> map (toSnapshotList >> List.map printableSnapshot) - |>> printResult - - |>- test "dinos, ordered by child 'height', limited to last 2" - ( once - (valueChanged (orderByChild "height" noRange (limitToLast 2))) - (dino |> sub "dinosaurs") - ) - |>> map (toValueList >> JE.list >> JE.encode 2) - |>> printString - - |>- createReporter "subscription results: dino scores, ordered by value, limited to first 3" - |>+ \reporterDino - -> test "subscribe dino scores, ordered by value, limited to first 3" - ( subscribe - (printableSnapshot >> Data >> reporterDino) - (Canceled >> reporterDino) - (childAdded (orderByValue noRange (limitToFirst 3))) - (dino |> sub "scores") - ) - |>> printResult - - |>- test "dinos, ordered by key, limited to first 2" - ( once - (valueChanged (orderByKey noRange (limitToFirst 2))) - (dino |> sub "dinosaurs") - ) - |>> map (toKeyList >> String.join " ") - |>> printString - - |>- test "order by priority" - ( once (valueChanged (orderByPriority noRange noLimit)) (parent loc) - ) - |>> map (toSnapshotList >> List.map .priority) - |>> printResult - - |>- test "order by priority, start at priority number 10" - ( once - (valueChanged - (orderByPriority - (startAt (NumberPriority 10, Nothing)) - noLimit - ) - ) - (parent loc) - ) - |>> map (toSnapshotList >> List.map .priority) - |>> printResult - - |>- test "order by priority, end at priority number 10" - ( once - (valueChanged - (orderByPriority - (endAt (NumberPriority 10, Nothing)) - noLimit - ) - ) - (parent loc) - ) - |>> map (toSnapshotList >> List.map .priority) - |>> printResult - - |>- test "order by child 'height', start at value 3, end at value 10" - ( once - (valueChanged - (orderByChild "height" - (range (JE.int 3) (JE.int 10)) - noLimit - ) - ) - (dino |> sub "dinosaurs") - ) - |>> map (toPairList >> JE.object >> JE.encode 2) - |>> printString - - |>- test "dinos, ordered by key, starting with letter 'l'" - ( once - (valueChanged - (orderByKey (range "l" "l~") noLimit) - ) - (dino |> sub "dinosaurs") - ) - |>> map (toKeyList >> String.join " ") - |>> printString - - |>- test "dinos, ordered by prioriy, start at NoPriority and key 's'" - ( once - (valueChanged - (orderByPriority - (startAt (NoPriority, (Just "s"))) - noLimit - ) - ) - (dino |> sub "dinosaurs") - ) - |>> map (toKeyList >> String.join " ") - |>> printString - - |>- test "end of test sequence" (Task.succeed ()) - |>- clear - ) port runTasks : Task Error () -port runTasks = runTest test1 +port runTasks = + runTest test1 + view : Html -> Html view testDisplay = - div [] - [ h1 [] [text "ElmFire Test"] - , div [] [ a [href url, target "_blank"] [text url] ] - , h2 [] [text "Test Report:"] - , testDisplay - ] + div [] + [ h1 [] [ text "ElmFire Test" ] + , div [] [ a [ href url, target "_blank" ] [ text url ] ] + , h2 [] [ text "Test Report:" ] + , testDisplay + ] + main : Signal Html -main = Signal.map view testDisplay +main = + Signal.map view testDisplay From c033c3ba8eb7c3944d126739eb2f55ed1a7c8c9b Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 14:20:23 +0100 Subject: [PATCH 08/23] Remove old test app --- test/Makefile | 45 ----- test/elm-package.json | 17 -- test/rules.json | 26 --- test/src/TaskTest.elm | 346 -------------------------------- test/src/Tests.elm | 455 ------------------------------------------ test/src/test.html | 67 ------- 6 files changed, 956 deletions(-) delete mode 100644 test/Makefile delete mode 100644 test/elm-package.json delete mode 100644 test/rules.json delete mode 100644 test/src/TaskTest.elm delete mode 100644 test/src/Tests.elm delete mode 100644 test/src/test.html diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index 9c54c95..0000000 --- a/test/Makefile +++ /dev/null @@ -1,45 +0,0 @@ - -ELM_MAIN = Tests -ELM_OUTPUT = elm.js -STATIC = test.html -BROWSER_TARGET = test.html - -SOURCE_DIR = src -BUILD_DIR = build - -### - -ELM_SOURCE = $(SOURCE_DIR)/$(ELM_MAIN).elm -ELM_BUILD = $(BUILD_DIR)/$(ELM_OUTPUT) -STATIC_SOURCES = $(STATIC:%=$(SOURCE_DIR)/%) - -### - -.PHONY: all open clean clean-build clean-all static elm new - -all: $(BUILD_DIR) $(ELM_BUILD) static - -new: clean-build all - -$(ELM_BUILD): elm - -elm: - elm make --yes --output $(ELM_BUILD) $(ELM_SOURCE) - -open: - xdg-open $(BUILD_DIR)/$(BROWSER_TARGET) 2>/dev/null - -static: # : $(BUILD_DIR)/% : $(SOURCE_DIR)/% $(BUILD_DIR) - rsync -rpE --ignore-missing-args stopgap $(STATIC_SOURCES) $(BUILD_DIR) - -$(BUILD_DIR): - mkdir $(BUILD_DIR) - -clean: - rm -rf elm-stuff - -clean-build: - rm -rf $(BUILD_DIR) - rm -rf elm-stuff/build-artifacts - -clean-all: clean-build clean diff --git a/test/elm-package.json b/test/elm-package.json deleted file mode 100644 index 1fce72c..0000000 --- a/test/elm-package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "version": "2.0.0", - "summary": "ElmFire test app", - "repository": "https://github.com/ThomasWeiser/elmfire.git", - "license": "BSD3", - "source-directories": [ - "src", - "../src" - ], - "exposed-modules": [], - "native-modules": true, - "dependencies": { - "elm-lang/core": "4.0.0 <= v < 5.0.0", - "elm-lang/html": "1.0.0 <= v < 2.0.0" - }, - "elm-version": "0.17.0 <= v < 0.18.0" -} diff --git a/test/rules.json b/test/rules.json deleted file mode 100644 index 24a6845..0000000 --- a/test/rules.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "rules": { - "unaccessible": { - ".read": false, - ".write": false - }, - "test": { - ".read": true, - ".write": true - }, - "dinosaur-facts": { - ".read": true, - ".write": false, - "dinosaurs": { - ".indexOn": "height" - }, - "scores": { - ".indexOn": ".value" - } - }, - "dict": { - ".read": true, - ".write": true - } - } -} diff --git a/test/src/TaskTest.elm b/test/src/TaskTest.elm deleted file mode 100644 index f8b3b3f..0000000 --- a/test/src/TaskTest.elm +++ /dev/null @@ -1,346 +0,0 @@ -module TaskTest - exposing - ( testDisplay - , runTest - , test - , sequence - , succeeds - , fails - , equals - , meets - , errorMeets - , clear - , createReporter - , map - , printMapResult - , printResult - , printString - , (|>>) - , (|>+) - , (|>-) - ) - -{- A Sketch of a testing framework for task-based code. - - This is work in progress. - We aim to make the logging output look much more nicer. --} - -import Signal exposing (Signal, Mailbox, mailbox) -import Dict exposing (Dict) -import Task exposing (Task, andThen, onError, fail, succeed) -import Html exposing (Html, div, span, text, a, h1, h2) -import Html.Attributes exposing (href, target, class) - - -------------------------------------------------------------------------------- - - -type Report - = Line String Activity String - - -type Activity - = RunSequence - | RunTask - | TestPass - | TestError - | TestPrint - | TestPrintPre - | RunReporter - - -reports : Mailbox (Maybe Report) -reports = - mailbox Nothing - - -report : String -> Activity -> String -> Task x () -report context activity text = - Signal.send reports.address <| - Just (Line context activity text) - - -type alias Model = - Dict String (List Report) - - -startModel : Model -startModel = - Dict.empty - - -progression : Maybe Report -> Model -> Model -progression maybeReport model = - case maybeReport of - Nothing -> - model - - Just report -> - case report of - Line context activity text -> - Dict.update - context - (\step -> - case step of - Nothing -> - Just [ report ] - - Just prevReports -> - Just (report :: prevReports) - ) - model - - -state : Signal Model -state = - Signal.foldp progression startModel reports.signal - - -testDisplay : Signal Html -testDisplay = - Signal.map view state - - - -------------------------------------------------------------------------------- - - -view : Model -> Html -view model = - div [ class "report" ] <| - List.map - viewStep - (Dict.values model) - - -viewStep : List Report -> Html -viewStep reports = - div [ class "step" ] - (List.foldl - -- reverses the list for display - (\report htmlList -> viewReport report :: htmlList) - [] - reports - ) - - -viewReport : Report -> Html -viewReport (Line context activity txt) = - div - [ class - (case activity of - RunSequence -> - "header-sequence" - - RunTask -> - "header-task" - - RunReporter -> - "header-reporter" - - TestPass -> - "test pass" - - TestError -> - "test error" - - TestPrint -> - "test print" - - TestPrintPre -> - "test print pre" - ) - ] - [ text txt ] - - - -------------------------------------------------------------------------------- - - -type alias Context = - String - - -type alias TestTask x a = - Context -> Task x a - - -runTest : TestTask x a -> Task x a -runTest testTask = - testTask "no test name" - - -test : String -> Task x a -> TestTask x a -test description task = - \context -> - report context RunTask description - `andThen` \_ -> task - - -createReporter : String -> TestTask y (b -> Task z ()) -createReporter description = - \context -> - report context RunReporter description - `andThen` \_ -> succeed (\val -> report context TestPrint (toString val)) - - -clear : TestTask y () -clear = - \context -> succeed () - - -succeeds : TestTask x a -> TestTask x a -succeeds testTask = - \context -> - (testTask context - `onError` - \err -> - (report context TestError "task failed" - `andThen` \_ -> fail err - ) - ) - `andThen` - \val -> - (report context TestPass "task succeeds" - `andThen` \_ -> succeed val - ) - - -fails : TestTask x a -> TestTask x a -fails testTask = - \context -> - (testTask context - `onError` - \err -> - (report context TestPass "task failed as expected" - `andThen` \_ -> fail err - ) - ) - `andThen` - \val -> - (report context TestError "task succeeds unexpectedly" - `andThen` \_ -> succeed val - ) - - -equals : String -> a -> TestTask x a -> TestTask x a -equals description expectedValue = - meets description ((==) expectedValue) - - -meets : String -> (a -> Bool) -> TestTask x a -> TestTask x a -meets description condition testTask = - \context -> - (testTask context - `onError` - \err -> - (report context TestError (description ++ " [task failed]") - `andThen` \_ -> fail err - ) - ) - `andThen` - \val -> - ((if condition val then - report context TestPass description - else - report context TestError (description ++ " [unfulfilled]") - ) - `andThen` \_ -> succeed val - ) - - -errorMeets : String -> (x -> Bool) -> TestTask x a -> TestTask x a -errorMeets description condition testTask = - \context -> - (testTask context - `onError` - \err -> - ((if condition err then - report context TestPass description - else - report context TestError (description ++ " [unfulfilled]") - ) - `andThen` \_ -> fail err - ) - ) - `andThen` - \val -> - (report context TestError (description ++ " [task succeeds unexpectedly]") - `andThen` \_ -> succeed val - ) - - -printMapResult : (a -> b) -> TestTask x a -> TestTask x a -printMapResult mapping testTask = - \context -> - (testTask context - `onError` - \err -> - (report context TestPrint ("Error: " ++ toString err) - `andThen` \_ -> fail err - ) - ) - `andThen` - \val -> - (report context TestPrint ("Result: " ++ toString (mapping val)) - `andThen` \_ -> succeed val - ) - - -printResult : TestTask x a -> TestTask x a -printResult testTask = - printMapResult identity testTask - - -printString : TestTask x String -> TestTask x String -printString testTask = - \context -> - testTask context - `andThen` - \str -> - (report context TestPrintPre str - `andThen` \_ -> succeed str - ) - - -map : (a -> b) -> TestTask x a -> TestTask x b -map func testTask = - \context -> - Task.map func (testTask context) - - -infixl 1 |>> -(|>>) : TestTask x a -> (TestTask x a -> TestTask x b) -> TestTask x b -(|>>) = - (|>) - - -infixl 0 |>- -(|>-) : TestTask x a -> TestTask y b -> TestTask y b -(|>-) testTask1 task2 = - \context -> - Task.toMaybe (testTask1 (context ++ "-1")) - `andThen` \_ -> (task2 (context ++ "-2")) - - -infixl 0 |>+ -(|>+) : TestTask x a -> (a -> TestTask x b) -> TestTask x b -(|>+) testTask1 callback2 = - \context -> - testTask1 (context ++ "+1") - `andThen` - \res1 -> - let - testTask2 = - callback2 res1 - in - testTask2 (context ++ "+2") - - -sequence : String -> TestTask x a -> TestTask x a -sequence name testTask = - \context -> - report name RunSequence name - `andThen` \_ -> testTask name diff --git a/test/src/Tests.elm b/test/src/Tests.elm deleted file mode 100644 index 00dadd1..0000000 --- a/test/src/Tests.elm +++ /dev/null @@ -1,455 +0,0 @@ -{- A Sketch of a Test App for ElmFire - - A given sequence of tasks is run on the Firebase API. - - This is work in progress. --} - - -module Main exposing (..) - -import String -import List -import Time -import Task exposing (Task) -import Json.Encode as JE -import Json.Decode as JD exposing ((:=)) -import Html exposing (Html, div, span, text, a, h1, h2) -import Html.Attributes exposing (href, target, class) -import TaskTest exposing (..) -import ElmFire exposing (..) -import ElmFire.Auth as Auth - - -------------------------------------------------------------------------------- --- Use this test Firebase. The tests below rely on some settings in this Firebase. --- Individual executions of this test suite use independent branches in this Firebase. - - -url : String -url = - "https://elmfiretest.firebaseio.com/" - - -dino : Location -dino = - fromUrl url |> sub "dinosaur-facts" - - - -------------------------------------------------------------------------------- - - -isNothing : Maybe a -> Bool -isNothing x = - case x of - Just _ -> - False - - Nothing -> - True - - -isJust : Maybe a -> Bool -isJust = - not << isNothing - - -isLocationError : Error -> Bool -isLocationError err = - err.tag == LocationError - - -isPermissionError : Error -> Bool -isPermissionError err = - err.tag == PermissionError - - -action1 : Maybe JE.Value -> Action -action1 maybeValue = - case maybeValue of - Just value -> - case JD.decodeValue JD.string value of - Ok str -> - Set (JE.string <| str ++ "!") - - _ -> - Remove - - _ -> - Abort - - -type alias PrintableSnapshot = - { subscription : Subscription - , key : String - , existing : Bool - , value : JE.Value - , prevKey : Maybe String - , priority : Priority - } - - -printableSnapshot : Snapshot -> PrintableSnapshot -printableSnapshot snap = - { subscription = snap.subscription - , key = snap.key - , existing = snap.existing - , value = snap.value - , prevKey = snap.prevKey - , priority = snap.priority - } - - -type Response - = NoResponse - | Data PrintableSnapshot - | Canceled Cancellation - - -test1 = - -- Start tests by opening a path and creating a reference for it ------------ - sequence "Test Sequence" - (test "open" (open (fromUrl url |> sub "test" |> push |> push)) - |>> succeeds - |>> meets "url of opened ref starts with base-url" (\ref -> url `String.startsWith` toUrl ref) - |>+ \ref -> - -- Connection state tests --------------------------------------------------- - createReporter "Metadata results" - |>+ \reporterConnected -> - test "subscribe to connection state changes" - (subscribeConnected - ((\state -> "connected: " ++ toString state) >> reporterConnected) - (location ref) - ) - |>> succeeds - |>- test "subscribe to serverTimeOffset changes" - (subscribeServerTimeOffset - ((\offset -> "serverTimeOffset: " ++ toString offset) >> reporterConnected) - (location ref) - ) - |>> succeeds - -- Create a new path in the Firebase for this test run ---------------------- - |>- - test "setWithPriority" (setWithPriority (JE.string "Hello") (NumberPriority 42) (location ref)) - |>> meets "set returned same ref" (\refReturned -> toUrl refReturned == toUrl ref) - |>> map location - |>+ \loc -> - test "once valueChanged (at child)" (once (valueChanged noOrder) loc) - |>> printMapResult printableSnapshot - |>> meets "once returned same key" (\snapshot -> snapshot.key == key ref) - |>- test "onDisconnectSet" - (onDisconnectSet (JE.string "disconnected") (loc |> parent |> sub "onlineState")) - |>> printResult - |>> succeeds - |>- test "onDisconnectSet has not written yet" - (once (valueChanged noOrder) (loc |> parent |> sub "onlineState")) - |>> meets "value is not existing" - (\snapshot -> not snapshot.existing) - |>> meets "value is null" - (\snapshot -> snapshot.value == JE.null) - |>- test "go offline" goOffline - |>> succeeds - |>- test "go online" goOnline - |>> succeeds - |>- test "onDisconnectSet has now written the value" - (once (valueChanged noOrder) (loc |> parent |> sub "onlineState")) - |>> meets "value is written" - (\snapshot -> snapshot.value == JE.string "disconnected") - -- Test updating ------------------------------------------------------------ - |>- - test "deep update" - (update - (JE.object [ ( "a/b", JE.string "AB" ) ]) - (loc |> parent) - `Task.andThen` - \_ -> - once - (valueChanged noOrder) - (loc |> parent |> sub "a/b") - ) - |>> meets "updated value ok" - (\snapshot -> snapshot.value == JE.string "AB") - -- User management tests ---------------------------------------------------- - |>- - test "generate a test email address from the resulting key string" - (Task.succeed <| (key ref) ++ "@b.com") - |>> printResult - |>+ \email -> - test "create user" (Auth.userOperation loc (Auth.createUser email "pw1")) - |>> printResult - |>> meets "returns a uid" isJust - |>- test "change password with wrong old password" (Auth.userOperation loc (Auth.changePassword email "wrong" "pw2")) - |>> errorMeets "reports AuthError InvalidPassword" - (\err -> err.tag == AuthError InvalidPassword) - |>- test "change password" (Auth.userOperation loc (Auth.changePassword email "pw1" "pw2")) - |>> printResult - |>> succeeds - |>- test "change email" (Auth.userOperation loc (Auth.changeEmail email "pw2" ("2" ++ email))) - |>> succeeds - {- Don't run this test by default (Firebase sends an nonaddressable email each time) - |>- test "reset password" (Auth.userOperation loc (Auth.resetPassword ("2" ++ email))) - |>> succeeds - -} - |>- - test "remove nonexistent user" (Auth.userOperation loc (Auth.removeUser (email) "pw2")) - |>> errorMeets "reports AuthError InvalidUser" - (\err -> err.tag == AuthError InvalidUser) - |>- test "remove user" (Auth.userOperation loc (Auth.removeUser ("2" ++ email) "pw2")) - |>> succeeds - -- Authentication tests ----------------------------------------------------- - |>- - createReporter "authSubscription results" - |>+ \reporterAuth -> - test "subscribe to authentication changes" - (Auth.subscribeAuth - reporterAuth - loc - ) - |>> succeeds - |>- test "unauthenticate" (Auth.unauthenticate loc) - |>> printResult - |>> succeeds - |>- test "getAuth while not authenticated" (Auth.getAuth loc) - |>> printResult - |>> equals "getAuth returns Nothing" Nothing - |>- test "getAuth with invalid location" (Auth.getAuth (loc |> root |> parent)) - |>> errorMeets "reports LocationError" isLocationError - |>- test "auth anonymously" - (Auth.authenticate loc [ Auth.rememberNone ] Auth.asAnonymous) - |>> printResult - |>- test "getAuth after authentication" (Auth.getAuth loc) - |>> printResult - |>> meets "getAuth returns an anonymous provider" - (\maybeAuth -> - case maybeAuth of - Just auth -> - auth.provider == "anonymous" - - _ -> - False - ) - |>- test "re-auth with wrong password" - (Auth.unauthenticate loc - `Task.andThen` - \_ -> - Auth.authenticate loc [ Auth.rememberNone ] (Auth.withPassword "a@b.com" "bad") - ) - |>> errorMeets "reports AuthError InvalidPassword" - (\err -> err.tag == AuthError InvalidPassword) - |>- test "re-auth with right password" - (Auth.unauthenticate loc - `Task.andThen` - \_ -> - Auth.authenticate loc [ Auth.rememberNone ] (Auth.withPassword "a@b.com" "good") - ) - |>> printResult - |>> meets "provider-specifics contain the given email address" - (\auth -> JD.decodeValue ("email" := JD.string) auth.specifics == Ok "a@b.com") - -- Test reading and writing (except complex queries) ------------------------ - |>- - test "once valueChanged (at child)" (once (valueChanged noOrder) loc) - |>> printMapResult printableSnapshot - |>> meets "once returned same key" (\snapshot -> snapshot.key == key ref) - |>> meets "once returned right value" (\snapshot -> snapshot.value == JE.string "Hello") - |>> meets "once returned right prevKey" (\snapshot -> snapshot.prevKey == Nothing) - |>> meets "once returned right priority" (\snapshot -> snapshot.priority == NumberPriority 42) - |>> map exportValue - |>> meets "export contains right value" - (\ex -> JD.decodeValue (".value" := JD.string) ex == Ok "Hello") - |>> meets "export contains right priority" - (\ex -> JD.decodeValue (".priority" := JD.int) ex == Ok 42) - |>- createReporter "subscription results" - |>+ \reporter1 -> - test "subscribe child added (at parent)" - (subscribe - (printableSnapshot >> Data >> reporter1) - (Canceled >> reporter1) - (childAdded noOrder) - (parent loc) - ) - |>> succeeds - |>> printResult - |>- test "sleep 1s" (Task.sleep (1 * Time.second)) - |>- test "set child with serverTimeStamp" (set serverTimeStamp (loc |> parent |> sub "server timestamp")) - |>- test "set another child" (set (JE.string "Elmers") (loc |> parent |> push)) - |>> map key - |>> printResult - |>+ \key -> - test "transaction on that child" - (transaction action1 (loc |> parent |> sub key) True) - |>> meets "committed and returned changed value" - (\( committed, snapshot ) -> - committed && snapshot.value == JE.string "Elmers!" - ) - |>> printMapResult (snd >> printableSnapshot) - |>- test "once valueChanged at non-existing location" - (once (valueChanged noOrder) (sub "_non_existing_key_" loc)) - |>> meets "returns non-existing" (\snapshot -> not snapshot.existing) - |>- test "set without permission" - (set (JE.null) (fromUrl url |> sub "unaccessible")) - |>> printResult - |>> fails - |>> errorMeets "reports PermissionError" isPermissionError - |>- clear - |>- test "once without permission" - (once (valueChanged noOrder) (fromUrl url |> sub "unaccessible")) - |>> printResult - |>> fails - |>> errorMeets "reports PermissionError" isPermissionError - |>- clear - |>- createReporter "subscription without permission results" - |>+ \reporter2 -> - test "subscribe without permission" - (subscribe - (printableSnapshot >> Data >> reporter2) - (Canceled >> reporter2) - (valueChanged noOrder) - (fromUrl url |> sub "unaccessible") - ) - |>> printResult - |>- clear - |>- test "transaction without permission" - (transaction action1 (fromUrl url |> sub "unaccessible") True) - |>> meets "not committed" (\( committed, _ ) -> not committed) - |>- clear - |>- test "open root's parent" (open (fromUrl url |> root |> parent)) - |>> printResult - |>> errorMeets "reports LocationError" isLocationError - |>- clear - |>- test "open an invalid URL" (open (fromUrl "not-a-url")) - |>> printResult - |>> errorMeets "reports LocationError" isLocationError - |>- clear - |>- test "subscribe with invalid URL" - (subscribe - (always Task.succeed ()) - (always Task.succeed ()) - (valueChanged noOrder) - (fromUrl "not-a-url") - ) - |>> printResult - |>> fails - |>> errorMeets "reports LocationError" isLocationError - |>- test "transaction with invalid URL" - (transaction action1 (fromUrl "not-a-url") True) - |>> printResult - |>> fails - |>> errorMeets "reports LocationError" isLocationError - |>- clear - -- Test complex queries, using the dino example data from Firebase docs ----- - |>- - test "dino test data" (once (valueChanged noOrder) dino) - |>> map (.value >> JE.encode 2) - |>> printString - |>- test "toSnapshotList" (once (valueChanged noOrder) (dino |> sub "scores")) - |>> map (toSnapshotList >> List.map printableSnapshot) - |>> printResult - |>- test "dinos, ordered by child 'height', limited to last 2" - (once - (valueChanged (orderByChild "height" noRange (limitToLast 2))) - (dino |> sub "dinosaurs") - ) - |>> map (toValueList >> JE.list >> JE.encode 2) - |>> printString - |>- createReporter "subscription results: dino scores, ordered by value, limited to first 3" - |>+ \reporterDino -> - test "subscribe dino scores, ordered by value, limited to first 3" - (subscribe - (printableSnapshot >> Data >> reporterDino) - (Canceled >> reporterDino) - (childAdded (orderByValue noRange (limitToFirst 3))) - (dino |> sub "scores") - ) - |>> printResult - |>- test "dinos, ordered by key, limited to first 2" - (once - (valueChanged (orderByKey noRange (limitToFirst 2))) - (dino |> sub "dinosaurs") - ) - |>> map (toKeyList >> String.join " ") - |>> printString - |>- test "order by priority" - (once (valueChanged (orderByPriority noRange noLimit)) (parent loc)) - |>> map (toSnapshotList >> List.map .priority) - |>> printResult - |>- test "order by priority, start at priority number 10" - (once - (valueChanged - (orderByPriority - (startAt ( NumberPriority 10, Nothing )) - noLimit - ) - ) - (parent loc) - ) - |>> map (toSnapshotList >> List.map .priority) - |>> printResult - |>- test "order by priority, end at priority number 10" - (once - (valueChanged - (orderByPriority - (endAt ( NumberPriority 10, Nothing )) - noLimit - ) - ) - (parent loc) - ) - |>> map (toSnapshotList >> List.map .priority) - |>> printResult - |>- test "order by child 'height', start at value 3, end at value 10" - (once - (valueChanged - (orderByChild "height" - (range (JE.int 3) (JE.int 10)) - noLimit - ) - ) - (dino |> sub "dinosaurs") - ) - |>> map (toPairList >> JE.object >> JE.encode 2) - |>> printString - |>- test "dinos, ordered by key, starting with letter 'l'" - (once - (valueChanged - (orderByKey (range "l" "l~") noLimit) - ) - (dino |> sub "dinosaurs") - ) - |>> map (toKeyList >> String.join " ") - |>> printString - |>- test "dinos, ordered by prioriy, start at NoPriority and key 's'" - (once - (valueChanged - (orderByPriority - (startAt ( NoPriority, (Just "s") )) - noLimit - ) - ) - (dino |> sub "dinosaurs") - ) - |>> map (toKeyList >> String.join " ") - |>> printString - |>- test "end of test sequence" (Task.succeed ()) - |>- clear - ) - - -port runTasks : Task Error () -port runTasks = - runTest test1 - - -view : Html -> Html -view testDisplay = - div [] - [ h1 [] [ text "ElmFire Test" ] - , div [] [ a [ href url, target "_blank" ] [ text url ] ] - , h2 [] [ text "Test Report:" ] - , testDisplay - ] - - -main : Signal Html -main = - Signal.map view testDisplay diff --git a/test/src/test.html b/test/src/test.html deleted file mode 100644 index e7ca438..0000000 --- a/test/src/test.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - -ElmFire Test - - - - - - - - From 0c2dec10e7258ce41fc0ee39f2df0b8db3d30f8b Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 14:31:28 +0100 Subject: [PATCH 09/23] Rename `sub` to `child` to avoid confusion with `subscription` --- src/ElmFire/LowLevel.elm | 24 ++++++++++++------------ src/Native/ElmFire.js | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ElmFire/LowLevel.elm b/src/ElmFire/LowLevel.elm index 739d700..edbd853 100644 --- a/src/ElmFire/LowLevel.elm +++ b/src/ElmFire/LowLevel.elm @@ -2,7 +2,7 @@ module ElmFire.LowLevel exposing ( Location , fromUrl - , sub + , child , parent , root , push @@ -72,7 +72,7 @@ module ElmFire.LowLevel ElmFire maps the Firebase JavaScript API to Elm functions and tasks. # Firebase Locations -@docs Location, fromUrl, sub, parent, root, push +@docs Location, fromUrl, child, parent, root, push # Firebase References @docs Reference, open, key, toUrl, location @@ -174,7 +174,7 @@ that represents a literal path into a firebase. A location can be constructed or obtained from - an absolute path by `fromUrl` -- relative to another location by `sub`, `parent`, `root`, `push` +- relative to another location by `child`, `parent`, `root`, `push` - a reference by `location` Locations are generally unvalidated until their use in a task. @@ -182,7 +182,7 @@ The constructor functions are pure. -} type Location = UrlLocation String - | SubLocation String Location + | ChildLocation String Location | ParentLocation Location | RootLocation Location | PushLocation Location @@ -251,7 +251,7 @@ type SnapshotFB = SnapshotFB -{-| Possible return values for update functions of a transaction +{-| Return values for update functions of a transaction -} type Action = Abort @@ -270,11 +270,11 @@ fromUrl = {-| Construct a location for the descendant at the specified relative path. - locUsers = sub "users" loc + locUsers = child "users" loc -} -sub : String -> Location -> Location -sub = - SubLocation +child : String -> Location -> Location +child = + ChildLocation {-| Construct the parent location from a child location. @@ -347,7 +347,7 @@ It can be used to check the location and to cache Firebase references. The task fails if the location construct is invalid. openTask = - (open <| sub user <| fromUrl "https://elmfire.firebaseio-demo.com/users") + (open <| child user <| fromUrl "https://elmfire.firebaseio-demo.com/users") `andThen` (\ref -> Signal.send userRefCache.address (user, ref)) -} open : Location -> Task Error Reference @@ -784,7 +784,7 @@ subscribeConnected createResponseTask location = ) (always (Task.succeed ())) (valueChanged noOrder) - (location |> root |> sub ".info/connected") + (location |> root |> child ".info/connected") {-| Subscribe to server time offset @@ -805,7 +805,7 @@ subscribeServerTimeOffset createResponseTask location = ) (always (Task.succeed ())) (valueChanged noOrder) - (location |> root |> sub ".info/serverTimeOffset") + (location |> root |> child ".info/serverTimeOffset") {-| A placeholder value for auto-populating the current timestamp diff --git a/src/Native/ElmFire.js b/src/Native/ElmFire.js index 5752477..ace48b3 100644 --- a/src/Native/ElmFire.js +++ b/src/Native/ElmFire.js @@ -81,7 +81,7 @@ var _ThomasWeiser$elmfire$Native_ElmFire = function () { case 'UrlLocation': ref = new Firebase (location._0); break; - case 'SubLocation': + case 'ChildLocation': ref = getRefStep (location._1) .child (location._0); break; case 'ParentLocation': From 78cd0c20d057c873d769ce81de51720253373b07 Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 14:35:27 +0100 Subject: [PATCH 10/23] Note in README about not updated content --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d868112..8d867af 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ _Demo application for these APIs: [Collaborative TodoMVC](https://github.com/Tho --- -_This library currently targets Elm version 0.16._ +_This README refers to the Elm 0.16 version of ElmFire._ -_A new version with an [effect manager](https://guide.elm-lang.org/effect_managers/index.html) for Elm 0.17/0.18 is under development. Please stay tuned!_ +_It needs to be updated to the new [effect manager](https://guide.elm-lang.org/effect_managers/index.html) version of ElmFire for Elm 0.17/0.18._ --- From c862d96fdfe53b08019acba8b7997b50ffc17181 Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 14:38:14 +0100 Subject: [PATCH 11/23] Simplified first version of effect module Simplifications: * Only subscription to valueChanged events * No query options * Fixed Firebase location --- src/ElmFire.elm | 176 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/ElmFire.elm diff --git a/src/ElmFire.elm b/src/ElmFire.elm new file mode 100644 index 0000000..d5c23ce --- /dev/null +++ b/src/ElmFire.elm @@ -0,0 +1,176 @@ +effect module ElmFire + where { subscription = MySub } + exposing + ( valueChanged + ) + +{-| +First sketch of an ElmFire effect module. + +Simplifications: +* Only valueChanged events +* No query options +* Fixed Firebase location + +Notes: +* Doesn't spawn a process like most other effect modules do. Ok? + +@docs valueChanged +-} + +-- import Dict +-- import Process +-- import Json.Decode as JD + +import Task exposing (Task) +import ElmFire.LowLevel as LL + + +-- SUBSCRIPTIONS + + +type MySub msg + = ValueChanged + -- Location + (Result LL.Error LL.Snapshot -> msg) + + +subMap : (a -> b) -> MySub a -> MySub b +subMap func sub = + case sub of + ValueChanged tagger -> + ValueChanged (tagger >> func) + + +{-| Subscribe to valueChanged ... +-} +valueChanged : + -- Location -> + (Result LL.Error LL.Snapshot -> msg) + -> Sub msg +valueChanged tagger = + subscription (ValueChanged tagger) + + + +-- MANAGER + + +type alias State msg = + { subs : SubsDict msg + } + + +type alias SubsDict msg = + -- Dict.Dict Location + Maybe (ValueChangedSubscription msg) + + +type alias ValueChangedSubscription msg = + { subscribers : List (Result LL.Error LL.Snapshot -> msg) + , lowLevelSubscription : LL.Subscription + } + + +init : Task Never (State msg) +init = + Task.succeed + (State + -- Dict.empty + Nothing + ) + + +(&>) : Task x a -> Task x b -> Task x b +(&>) t1 t2 = + Task.andThen t1 (\_ -> t2) + + +type SelfMsg + = NewSnapshot LL.Snapshot + + + +-- NewLowLevelSub Subscription + + +onEffects : + Platform.Router msg SelfMsg + -> List (MySub msg) + -> State msg + -> Task Never (State msg) +onEffects router mySubs state = + case ( mySubs, state.subs ) of + ( [], Nothing ) -> + Task.succeed state + + ( [], Just { subscribers, lowLevelSubscription } ) -> + LL.unsubscribe lowLevelSubscription + |> Task.map + (\_ -> { subs = Nothing }) + |> (flip Task.onError) + (\llError -> + Task.succeed { subs = Nothing } + -- TODO: Handle error + ) + + ( _ :: _, Nothing ) -> + (LL.subscribe + (\snapshot -> Platform.sendToSelf router (NewSnapshot snapshot)) + (\cancellation -> Task.succeed ()) + -- TODO: Handle cancellation + (LL.valueChanged LL.noOrder) + (LL.fromUrl "https://elmfiretest.firebaseio.com/test") + ) + |> Task.map + (\lowLevelSubscription -> + { subs = + Just + { subscribers = buildSubscriberList mySubs + , lowLevelSubscription = lowLevelSubscription + } + } + ) + |> (flip Task.onError) + (\llError -> + Task.succeed { subs = Nothing } + -- TODO: Handle error + ) + + ( _ :: _, Just { subscribers, lowLevelSubscription } ) -> + Task.succeed + { subs = + (Just + { subscribers = buildSubscriberList mySubs + , lowLevelSubscription = lowLevelSubscription + } + ) + } + + +buildSubscriberList : List (MySub msg) -> List (Result LL.Error LL.Snapshot -> msg) +buildSubscriberList mySubs = + List.map + (\mySub -> + case mySub of + ValueChanged tagger -> + tagger + ) + mySubs + + +onSelfMsg : + Platform.Router msg SelfMsg + -> SelfMsg + -> State msg + -> Task Never (State msg) +onSelfMsg router selfMsg state = + case ( selfMsg, state.subs ) of + ( _, Nothing ) -> + Task.succeed state + + ( NewSnapshot snapshot, Just { subscribers, lowLevelSubscription } ) -> + subscribers + |> List.map (\tagger -> Platform.sendToApp router (tagger (Ok snapshot))) + |> Task.sequence + |> Task.map (\_ -> state) From ad3d8680ae195b7b23b9218ef0d31aad0962290e Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 14:40:52 +0100 Subject: [PATCH 12/23] Minimal test app for effect manager --- test/elm-package.json | 18 ++ test/index.html | 37 ++++ test/modd.conf | 17 ++ test/src/Test.elm | 438 ++++++++++++++++++++++++++++++++++++++++++ test/start.sh | 20 ++ 5 files changed, 530 insertions(+) create mode 100644 test/elm-package.json create mode 100644 test/index.html create mode 100644 test/modd.conf create mode 100644 test/src/Test.elm create mode 100755 test/start.sh diff --git a/test/elm-package.json b/test/elm-package.json new file mode 100644 index 0000000..dcda72a --- /dev/null +++ b/test/elm-package.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "summary": "Test for Firebase Bindings for Elm", + "repository": "https://github.com/ThomasWeiser/elmfire.git", + "license": "BSD3", + "source-directories": [ + "src", + "../src" + ], + "exposed-modules": [], + "native-modules": true, + "dependencies": { + "elm-lang/core": "4.0.5 <= v < 5.0.0", + "elm-lang/html": "1.1.0 <= v < 2.0.0", + "evancz/elm-http": "3.0.1 <= v < 4.0.0" + }, + "elm-version": "0.17.0 <= v < 0.18.0" +} diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..15e0466 --- /dev/null +++ b/test/index.html @@ -0,0 +1,37 @@ + + + + + + + + +
+ + + + + + + diff --git a/test/modd.conf b/test/modd.conf new file mode 100644 index 0000000..d917ebd --- /dev/null +++ b/test/modd.conf @@ -0,0 +1,17 @@ + +# 2016-09-05 +# devd reloading doesn't work on html files generated by elm-make. +# So we need separated files index.html and generated elm.js + +../src/**/*.elm ../src/**/Native/*.js src/**/*.elm { + prep: elm make --warn --yes --output build/elm.js src/Test.elm +} + +# test/static/** { +# prep: cp -rp test/static/* build/ +# } + +# Don't start devd here as a daemon. This generates too much unwanted output. +# index.html build/elm.js { +# daemon: devd -mqo . +# } diff --git a/test/src/Test.elm b/test/src/Test.elm new file mode 100644 index 0000000..6df0d73 --- /dev/null +++ b/test/src/Test.elm @@ -0,0 +1,438 @@ +module Main exposing (..) + +import Array exposing (Array) +import Html exposing (..) +import Html.Attributes as HA +import Html.App as App +import Task +import Process +import Time exposing (Time) +import Json.Encode as JE +import Json.Decode as JD +import Date +import ElmFire.LowLevel as LL +import ElmFire + + +testUrl : String +testUrl = + "https://elmfiretest.firebaseio.com/test" + + +testLocation : LL.Location +testLocation = + LL.fromUrl testUrl + + +main : Program Never +main = + App.program + { init = init + , view = view + , update = update + , subscriptions = subscriptions + } + + +type Response + = ValueChanged (Result LL.Error LL.Snapshot) + + +responseIsValueChanged : Response -> Bool +responseIsValueChanged response = + True + + +type alias Model = + { startDate : String + , keyPushed : String + , log : Array LogEntry + , subs : List (Sub Action) + , waiting : Maybe ( Int, Response -> Bool, Action ) + , responses : Maybe (List Response) + } + + +type LogEntry + = LogString String + | LogStep String + | LogSnapshot LL.Snapshot + + +type Action + = End + | Fatal String + | SubResponse Response + | Timeout Step + | Step Step + + +type Step + = SetDate String + | Clear + | Subscribe + | Timeout1 + | CheckResponse1 + | Push + | Once LL.Reference + | Check LL.Snapshot + | CheckResponse2 + + +init : ( Model, Cmd Action ) +init = + ( { startDate = "[Date.now]" + , keyPushed = "" + , log = Array.empty + , subs = [] + , waiting = Nothing + , responses = Nothing + } + , Task.perform + (\_ -> Fatal "Task Date.now failed. Should never happen.") + (\date -> Step (SetDate (toString date))) + Date.now + ) + + +next : Action -> Cmd Action +next action = + Task.perform + (\_ -> Debug.crash "Task.succeed failed.") + identity + (Task.succeed action) + + +nextStep : Step -> Cmd Action +nextStep step = + next (Step step) + + +defer : Time -> Action -> Cmd Action +defer time action = + Task.perform + (\_ -> Debug.crash "Process.sleep failed.") + identity + (Process.sleep time + |> Task.map (always action) + ) + + +await : Time -> Int -> (Response -> Bool) -> Step -> Step -> Model -> ( Model, Cmd Action ) +await maxTime numberOfResponses filter onTimeout onSuccess model = + case model.waiting of + Just _ -> + Debug.crash "Already waiting" + + Nothing -> + if numberOfResponses < 0 then + Debug.crash "Cannot await a negative number of responses" + else if numberOfResponses == 0 then + ( model, nextStep onSuccess ) + else + ( { model | waiting = Just ( numberOfResponses, filter, Step onSuccess ) } + |> display (LogString ("awaiting " ++ toString numberOfResponses ++ " responses with timeout " ++ toString maxTime)) + , defer maxTime (Timeout onTimeout) + ) + + +gatherResponses : Model -> Model +gatherResponses model = + { model | responses = Just [] } + + +ignoreResponses : Model -> Model +ignoreResponses model = + { model | responses = Nothing } + + +noticeResponse : Response -> Model -> ( Model, Cmd Action ) +noticeResponse response model = + let + model1 = + case model.responses of + Nothing -> + model + + Just priorResponses -> + { model | responses = Just (response :: priorResponses) } + in + case model1.waiting of + Nothing -> + ( model1, Cmd.none ) + + Just ( number, filter, action ) -> + if filter response then + if number > 1 then + ( { model1 | waiting = Just ( number - 1, filter, action ) } + , Cmd.none + ) + else + ( { model1 | waiting = Nothing } + , next action + ) + else + ( model1, Cmd.none ) + + +testResponses : (List Response -> Maybe String) -> Model -> Model +testResponses testFunction model = + model + |> (case model.responses of + Nothing -> + display <| LogString "Bad test sequence: Testing responses without gathering them." + + Just responses -> + display <| + LogString + (case testFunction responses of + Nothing -> + "testResponses passes" + + Just message -> + "testResponses fails: " ++ message + ) + ) + |> ignoreResponses + + +display : LogEntry -> Model -> Model +display logEntry model = + { model | log = Array.push logEntry model.log } + + +update : Action -> Model -> ( Model, Cmd Action ) +update action model = + case action of + End -> + ( display (LogStep "End of test sequence") model + , Cmd.none + ) + + Fatal description -> + ( display (LogString ("Fatal: " ++ description)) model + , Cmd.none + ) + + SubResponse response -> + model + |> display (LogString "got subscription response: (...TODO) ") + |> noticeResponse response + + Timeout timeoutStep -> + case model.waiting of + Nothing -> + ( model, Cmd.none ) + + Just ( number, _, _ ) -> + ( { model | waiting = Nothing } + |> display + (LogString + ("timeout while waiting for " + ++ if number == 1 then + "response" + else + (toString number) ++ " more responses" + ) + ) + , nextStep timeoutStep + ) + + Step step -> + updateStep step model + + +updateStep : Step -> Model -> ( Model, Cmd Action ) +updateStep step model = + case step of + SetDate dateString -> + ( { model | startDate = dateString } + |> display (LogStep "SetDate") + , nextStep Clear + ) + + Clear -> + ( model |> display (LogStep "Clear") |> gatherResponses + , Task.perform + (\error -> Fatal error.description) + (\ref -> Step Subscribe) + (LL.remove testLocation) + -- (LL.remove (testLocation |> LL.child "test the test: don't remove path")) + ) + + Subscribe -> + { model | subs = [ ElmFire.valueChanged (SubResponse << ValueChanged) ] } + |> display (LogStep "Subscribing to valueChanges") + |> await 4000 1 responseIsValueChanged Timeout1 CheckResponse1 + + Timeout1 -> + ( model + |> display (LogString "Missing initial response from valueChanged subscription") + , nextStep Push + ) + + CheckResponse1 -> + ( model + |> testResponses check_ValueChanged_NonExisting + , nextStep Push + ) + + Push -> + ( model + |> display (LogStep "About to push") + |> gatherResponses + , Task.perform + (\error -> Fatal error.description) + (\ref -> Step (Once ref)) + (LL.set + (JE.string model.startDate) + -- (JE.string (model.startDate ++ " BUG-TEST")) + (testLocation |> LL.push) + ) + ) + + Once ref -> + ( { model | keyPushed = LL.key ref } + |> display (LogString ("pushed to: " ++ LL.toUrl ref)) + , Task.perform + (\error -> Fatal error.description) + (\snap -> Step (Check snap)) + (LL.once + (LL.valueChanged LL.noOrder) + (LL.location ref) + ) + ) + + Check snap -> + ( model + |> display (LogSnapshot snap) + |> display + (LogString + (case JD.decodeValue JD.string snap.value of + Err err -> + err + + Ok string -> + if string == model.startDate then + "Value as expected" + else + "Unexpected value" + ) + ) + , nextStep CheckResponse2 + ) + + CheckResponse2 -> + ( model + |> testResponses (check_ValueChanged_Key_String ( model.keyPushed, model.startDate )) + , next End + ) + + +check_ValueChanged_NonExisting : List Response -> Maybe String +check_ValueChanged_NonExisting responses = + case responses of + [] -> + Just "Missing response from valueChanged subscription" + + [ ValueChanged (Ok { existing }) ] -> + if existing then + Just "got a value, should be non-existing" + else + Nothing + + _ :: _ -> + Just "Got more than one response" + + +check_ValueChanged_Key_String : ( String, String ) -> List Response -> Maybe String +check_ValueChanged_Key_String ( key, str ) responses = + case responses of + [] -> + Just "Missing response from valueChanged subscription" + + [ ValueChanged (Ok { value }) ] -> + case + JD.decodeValue + (JD.at [ key ] JD.string) + value + of + Ok s -> + if s == str then + Nothing + else + Just "Unexpected value in response" + + Err err -> + Just ("Unexpected value in response: " ++ err) + + [ response ] -> + Just "Unexpected response" + + _ :: _ -> + Just "Got more than one response" + + +subscriptions : Model -> Sub Action +subscriptions model = + Sub.batch model.subs + + +view : Model -> Html Action +view model = + div [] + [ div [] + [ a [ HA.href testUrl, HA.target "_blank" ] [ text testUrl ] ] + , div [] [ text model.startDate ] + , hr [] [] + , div [] (viewLog model.log) + ] + + +viewLog : Array LogEntry -> List (Html Action) +viewLog log = + List.map viewLogEntry (Array.toList log) + + +viewLogEntry : LogEntry -> Html Action +viewLogEntry entry = + case entry of + LogString logText -> + div [] [ text logText ] + + LogStep logText -> + div + [ HA.class "step" ] + [ text logText ] + + LogSnapshot snapshot -> + viewSnapshot snapshot + + +viewSnapshot : LL.Snapshot -> Html Action +viewSnapshot { key, existing, value, prevKey, priority } = + table [] + [ thead [] + [ tr [] + [ th [] [ text "existing" ] + , th [] [ text "key" ] + , th [] [ text "value" ] + , th [] [ text "prevKey" ] + , th [] [ text "priority" ] + ] + ] + , tbody [] + [ tr [] + [ td [] [ text (toString existing) ] + , td [] [ text key ] + , td [] [ text (toString value) ] + , td [] [ text (toString prevKey) ] + , td [] [ text (toString priority) ] + ] + ] + ] + + +viewError : LL.Error -> Html Action +viewError { description } = + text description diff --git a/test/start.sh b/test/start.sh new file mode 100755 index 0000000..0be6bcb --- /dev/null +++ b/test/start.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -e + +mkdir -p build + +# Use devd for serving and reloading in browser +# Start devd here (and not as a modd daemon via modd.conf) +# Reason is we don't want to see verbose output +devd -olq . & + +# Kill devd when this scripts exits. +# Warning: Will kill the browser too, it it is newly opened by "devd -o" +# http://stackoverflow.com/q/360201/2171779 +devd_pid=$! +trap "echo 1; exit" INT TERM +trap "echo 2; kill -SIGHUP $devd_pid" EXIT + +# Compile elm code. Configured in modd.conf +modd From 532fd3d3cb35a59705ec12d6669fc5a55c689dee Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 18:15:45 +0100 Subject: [PATCH 13/23] Use a list type to make type Location comparable Unfortunately we cannot use a union type for Location as we did before. The implementation of the effect manager demands Location to be a comparable type. Union types are not comparable (in Elm 0.17). Lists and tuples are the only aggregate types that transports comparability of its element type. --- src/ElmFire/LowLevel.elm | 41 +++++++++++++++++++---------------- src/ElmFire/Types.elm | 14 ++++++++++++ src/Native/ElmFire.js | 46 +++++++++++++++++++++------------------- 3 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 src/ElmFire/Types.elm diff --git a/src/ElmFire/LowLevel.elm b/src/ElmFire/LowLevel.elm index edbd853..6e673e2 100644 --- a/src/ElmFire/LowLevel.elm +++ b/src/ElmFire/LowLevel.elm @@ -118,6 +118,7 @@ ElmFire maps the Firebase JavaScript API to Elm functions and tasks. @docs Error, ErrorType, AuthErrorType -} +import ElmFire.Types exposing (..) import Native.Firebase import Native.ElmFire import Time exposing (Time) @@ -181,12 +182,16 @@ Locations are generally unvalidated until their use in a task. The constructor functions are pure. -} type Location - = UrlLocation String - | ChildLocation String Location - | ParentLocation Location - | RootLocation Location - | PushLocation Location - | RefLocation Reference + = Location LocationSpec + + + +{- Unfortunately we cannot use a union type for Location. + + The implementation of the effect manager demands Location to be a comparable type. + Union types are not comparable (in Elm 0.17). + Lists are the only aggregate type that transports comparability of its element type. +-} {-| A Firebase reference, which is an opaque type that represents an opened path. @@ -264,8 +269,8 @@ type Action loc = fromUrl "https://elmfire.firebaseio-demo.com/foo/bar" -} fromUrl : String -> Location -fromUrl = - UrlLocation +fromUrl url = + Location [ ( "url", url ) ] {-| Construct a location for the descendant at the specified relative path. @@ -273,8 +278,8 @@ fromUrl = locUsers = child "users" loc -} child : String -> Location -> Location -child = - ChildLocation +child name (Location list) = + Location (( "child", name ) :: list) {-| Construct the parent location from a child location. @@ -282,8 +287,8 @@ child = loc2 = parent loc1 -} parent : Location -> Location -parent = - ParentLocation +parent (Location list) = + Location (( "parent", "" ) :: list) {-| Construct the root location from descendant location @@ -291,8 +296,8 @@ parent = loc2 = root loc1 -} root : Location -> Location -root = - RootLocation +root (Location list) = + Location (( "root", "" ) :: list) {-| Construct a new child location using a to-be-generated key. @@ -308,8 +313,8 @@ and get its name. set val (push loc) `andThen` (\ref -> ... key ref ...) -} push : Location -> Location -push = - PushLocation +push (Location list) = + Location (( "push", "" ) :: list) {-| Obtain a location from a reference. @@ -317,8 +322,8 @@ push = reference = location loc -} location : Reference -> Location -location = - RefLocation +location ref = + fromUrl <| Native.ElmFire.toUrl ref {-| Get the url of a reference. diff --git a/src/ElmFire/Types.elm b/src/ElmFire/Types.elm new file mode 100644 index 0000000..ad6245d --- /dev/null +++ b/src/ElmFire/Types.elm @@ -0,0 +1,14 @@ +module ElmFire.Types exposing (LocationSpec) + +{- Internal representation of locations + + Unfortunately we cannot use a union type here. + + The implementation of the effect manager demands Location to be a comparable type. + Union types are not comparable (in Elm 0.17). + Lists are the only aggregate type that transports comparability of its element type. +-} + + +type alias LocationSpec = + List ( String, String ) diff --git a/src/Native/ElmFire.js b/src/Native/ElmFire.js index ace48b3..a3d925d 100644 --- a/src/Native/ElmFire.js +++ b/src/Native/ElmFire.js @@ -75,29 +75,31 @@ var _ThomasWeiser$elmfire$Native_ElmFire = function () { }; } - function getRefStep (location) { + function getRefStep (locationList) { var ref; - switch (location.ctor) { - case 'UrlLocation': - ref = new Firebase (location._0); - break; - case 'ChildLocation': - ref = getRefStep (location._1) .child (location._0); - break; - case 'ParentLocation': - ref = getRefStep (location._0) .parent (); - if (! ref) { throw ('Error: Root has no parent'); } - break; - case 'RootLocation': - ref = getRefStep (location._0) .root (); - break; - case 'PushLocation': - ref = getRefStep (location._0) .push (); - break; - case 'RefLocation': - ref = location._0; - break; + if (locationList.ctor === '::') { + var head = locationList._0; + var rest = locationList._1; + switch (head._0) { + case 'url': + ref = new Firebase (head._1); + break; + case 'child': + ref = getRefStep (rest) .child (head._1); + break; + case 'parent': + ref = getRefStep (rest) .parent (); + if (! ref) { throw ('Error: Root has no parent'); } + break; + case 'root': + ref = getRefStep (rest) .root (); + break; + case 'push': + ref = getRefStep (rest) .push (); + break; + } } + if (! ref) { throw ('Bad Firebase reference.' + pleaseReportThis); } @@ -107,7 +109,7 @@ var _ThomasWeiser$elmfire$Native_ElmFire = function () { function getRef (location, failureCallback) { var ref; try { - ref = getRefStep (location); + ref = getRefStep (location._0); } catch (exception) { failureCallback (_elm_lang$core$Native_Scheduler.fail (error2elm ('LocationError', exception.toString ()))); From bb0cbfd31577657f7fe53c08b508352fc52f7962 Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 6 Dec 2016 18:19:06 +0100 Subject: [PATCH 14/23] Remove out-of-date demo --- demo/Makefile | 45 ----- demo/elm-package.json | 17 -- demo/src/Demo.elm | 436 ------------------------------------------ demo/src/demo.html | 23 --- 4 files changed, 521 deletions(-) delete mode 100644 demo/Makefile delete mode 100644 demo/elm-package.json delete mode 100644 demo/src/Demo.elm delete mode 100644 demo/src/demo.html diff --git a/demo/Makefile b/demo/Makefile deleted file mode 100644 index 9a4e115..0000000 --- a/demo/Makefile +++ /dev/null @@ -1,45 +0,0 @@ - -ELM_MAIN = Demo -ELM_OUTPUT = elm.js -STATIC = demo.html -BROWSER_TARGET = demo.html - -SOURCE_DIR = src -BUILD_DIR = build - -### - -ELM_SOURCE = $(SOURCE_DIR)/$(ELM_MAIN).elm -ELM_BUILD = $(BUILD_DIR)/$(ELM_OUTPUT) -STATIC_SOURCES = $(STATIC:%=$(SOURCE_DIR)/%) - -### - -.PHONY: all open clean clean-build clean-all static elm new - -all: $(BUILD_DIR) $(ELM_BUILD) static - -new: clean-build all - -$(ELM_BUILD): elm - -elm: - elm make --yes --output $(ELM_BUILD) $(ELM_SOURCE) - -open: - xdg-open $(BUILD_DIR)/$(BROWSER_TARGET) 2>/dev/null - -static: # : $(BUILD_DIR)/% : $(SOURCE_DIR)/% $(BUILD_DIR) - rsync -rpE --ignore-missing-args stopgap $(STATIC_SOURCES) $(BUILD_DIR) - -$(BUILD_DIR): - mkdir $(BUILD_DIR) - -clean: - rm -rf elm-stuff - -clean-build: - rm -rf $(BUILD_DIR) - rm -rf elm-stuff/build-artifacts - -clean-all: clean-build clean diff --git a/demo/elm-package.json b/demo/elm-package.json deleted file mode 100644 index 32c7d86..0000000 --- a/demo/elm-package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "version": "2.0.0", - "summary": "ElmFire demo app", - "repository": "https://github.com/ThomasWeiser/elmfire.git", - "license": "BSD3", - "source-directories": [ - "src", - "../src" - ], - "exposed-modules": [], - "native-modules": true, - "dependencies": { - "elm-lang/core": "4.0.0 <= v < 5.0.0", - "elm-lang/html": "1.0.0 <= v < 2.0.0" - }, - "elm-version": "0.17.0 <= v < 0.18.0" -} diff --git a/demo/src/Demo.elm b/demo/src/Demo.elm deleted file mode 100644 index 795d3fd..0000000 --- a/demo/src/Demo.elm +++ /dev/null @@ -1,436 +0,0 @@ -{- A ElmFire Demo App - - A given sequence of tasks is run on the Firebase API. - Steps and results are logged as Html. - - This is work in progress. - We aim to make the logging output look much more nicer. --} - - -module Main exposing (..) - -import Signal exposing (Signal, Mailbox, mailbox, message) -import Task exposing (Task, andThen, onError, fail, succeed, sleep) -import Json.Encode as JE -import Time -import Html exposing (Html, div, span, input, output, label, text, a, h1, h2) -import Html.Attributes exposing (href, target, class) -import ElmFire - exposing - ( fromUrl - , toUrl - , key - , sub - , parent - , root - , push - , location - , open - , set - , setWithPriority - , setPriority - , update - , remove - , subscribe - , unsubscribe - , once - , valueChanged - , childAdded - , childChanged - , childRemoved - , childMoved - , noOrder - , noLimit - , Location - , Reference - , Priority(..) - , Cancellation(..) - , Snapshot - , Subscription - , Error - , Query - ) - - -------------------------------------------------------------------------------- - - -url : String -url = - "https://elmfire.firebaseio-demo.com/demo" - - - -------------------------------------------------------------------------------- - - -type Response - = NoResponse - | Data Snapshot - | Canceled Cancellation - - -responses : Signal.Mailbox Response -responses = - Signal.mailbox NoResponse - - -type LogEntry - = LogNone - | LogTaskStart String - | LogTaskSuccess String String - | LogTaskFailure String String - | LogResponse Response - - -notes : Signal.Mailbox LogEntry -notes = - Signal.mailbox LogNone - - -logEntries : Signal LogEntry -logEntries = - Signal.merge - notes.signal - (Signal.map LogResponse responses.signal) - - -type alias LogList = - List LogEntry - - -type alias TaskList = - List ( String, LogEntry ) - - -type alias Model = - { log : LogList - , tasks : TaskList - } - - -startModel : Model -startModel = - { log = [], tasks = [] } - - -progression : LogEntry -> Model -> Model -progression note model = - { log = note :: model.log - , tasks = - case note of - LogTaskStart step -> - replaceOrAppend step note model.tasks - - LogTaskSuccess step _ -> - replaceOrAppend step note model.tasks - - LogTaskFailure step _ -> - replaceOrAppend step note model.tasks - - otherwise -> - model.tasks - } - - -replaceOrAppend : String -> LogEntry -> TaskList -> TaskList -replaceOrAppend step note tasks = - case tasks of - [] -> - [ ( step, note ) ] - - ( s1, n1 ) :: rest -> - if s1 == step then - ( step, note ) :: rest - else - ( s1, n1 ) :: replaceOrAppend step note rest - - -state : Signal Model -state = - Signal.foldp progression startModel logEntries - - -view : Model -> Html -view model = - div [] - [ h1 [] [ text "ElmFire Demo" ] - , div [] [ a [ href url, target "_blank" ] [ text url ] ] - , div [ class "tasks" ] (h2 [] [ text "Tasks" ] :: viewTasks model.tasks) - , div [ class "logs" ] (h2 [] [ text "Log" ] :: viewLog model.log) - ] - - -viewLog : LogList -> List Html -viewLog log = - List.foldl - -- reverses the list for display - (\entry htmlList -> - let - maybeHtml = - viewLogEntry entry - in - case maybeHtml of - Nothing -> - htmlList - - Just html -> - html :: htmlList - ) - [] - log - - -viewTasks : TaskList -> List Html -viewTasks = - List.map - (\( step, logEntry ) -> - div [ class "line" ] - [ span [] [ text step ] - , case logEntry of - LogTaskStart _ -> - span [ class "started" ] [ text "..." ] - - LogTaskSuccess _ res -> - span [ class "success" ] [ text res ] - - LogTaskFailure _ err -> - span [ class "failure" ] [ text err ] - - otherwise -> - text "" - ] - ) - - -viewLogEntry : LogEntry -> Maybe Html -viewLogEntry logEntry = - let - line c s t = - div [ class "line" ] [ span [] [ text s ], span [ class c ] [ text t ] ] - in - case logEntry of - LogNone -> - Nothing - - LogTaskStart step -> - Just <| line "started" step "started" - - LogTaskSuccess step res -> - Just <| line "success" step res - - LogTaskFailure step err -> - Just <| line "failure" step err - - LogResponse response -> - case response of - NoResponse -> - Nothing - - Data snapshot -> - Just <| line "response" (toString snapshot.subscription) (viewSnapshot snapshot) - - Canceled cancellation -> - Just <| - case cancellation of - Unsubscribed id -> - line "canceled" (toString id) "unsubscribed" - - QueryError id err -> - line "canceled" (toString id) ("queryError: " ++ toString err) - - -viewSnapshot : Snapshot -> String -viewSnapshot snapshot = - let - k = - key snapshot.reference - in - (if k == "" then - "(root)" - else - k - ) - ++ ": " - ++ (viewValue snapshot.value) - - -viewValue : JE.Value -> String -viewValue value = - -- Comparing JE.null throws a runtime error, - -- see https://github.com/elm-lang/core/pull/294 - -- if JE.null == value then "no value" else JE.encode 0 value - JE.encode 0 value - - -main : Signal Html -main = - Signal.map view state - - - -------------------------------------------------------------------------------- - - -intercept : (v -> String) -> String -> Task Error v -> Task Error v -intercept valueToString step task = - Signal.send notes.address (LogTaskStart step) - `andThen` - \_ -> - (task - `onError` - \err -> - Signal.send notes.address (LogTaskFailure step (toString err)) - `andThen` - \_ -> - fail err - ) - `andThen` - \val -> - Signal.send notes.address (LogTaskSuccess step (valueToString val)) - `andThen` - \_ -> - succeed val - - - -------------------------------------------------------------------------------- - - -doSet : String -> JE.Value -> Location -> Task Error Reference -doSet step value location = - intercept (always "synced") step (set value location) - - -doSetPriority : String -> Priority -> Location -> Task Error Reference -doSetPriority step priority location = - intercept (always "synced") step (setPriority priority location) - - -doUpdate : String -> JE.Value -> Location -> Task Error Reference -doUpdate step value location = - intercept (always "synced") step (update value location) - - -doRemove : String -> Location -> Task Error Reference -doRemove step location = - intercept (always "synced") step (remove location) - - -doSubscribe : String -> Query -> Location -> Task Error Subscription -doSubscribe step query location = - intercept toString - step - (subscribe - (Signal.send responses.address << Data) - (Signal.send responses.address << Canceled) - query - location - ) - - -doUnsubscribe : String -> Subscription -> Task Error () -doUnsubscribe step subscription = - intercept (always "done") step (unsubscribe subscription) - - -doOnce : String -> Query -> Location -> Task Error JE.Value -doOnce step query location = - intercept viewValue - step - (once query location - `andThen` \snapshot -> succeed snapshot.value - ) - - -doSleep : String -> Float -> Task () () -doSleep id seconds = - let - step = - "sleep " ++ id ++ " for " ++ (toString seconds) ++ " seconds" - in - Signal.send notes.address (LogTaskStart step) - `andThen` - \_ -> - sleep (seconds * Time.second) - `andThen` \_ -> Signal.send notes.address (LogTaskSuccess step "awake") - - -doShowRefLocation : String -> Reference -> Task e () -doShowRefLocation id ref = - Signal.send notes.address (LogTaskSuccess id (location ref |> toString)) - - -doRefUrl : String -> Reference -> Task e () -doRefUrl id ref = - Signal.send notes.address (LogTaskSuccess id (toUrl ref)) - - -doRefKey : String -> Reference -> Task e () -doRefKey id ref = - Signal.send notes.address (LogTaskSuccess id (key ref)) - - - -------------------------------------------------------------------------------- - - -andAnyway : Task x a -> Task y b -> Task y b -andAnyway task1 task2 = - (Task.map (\_ -> ()) task1 `onError` (\_ -> succeed ())) - `andThen` (\_ -> task2) - - -port runTasks : Task () () -port runTasks = - let - loc = - fromUrl url - in - doSubscribe "query1 value" (valueChanged noOrder) loc - `andAnyway` (Task.spawn <| doSet "async set1 value" (JE.string "start") loc) - `andAnyway` - doSubscribe "query2 parent value" - (valueChanged noOrder) - (loc |> parent) - `andAnyway` doSleep "1" 2 - `andAnyway` open (push loc) - `andThen` - (\ref -> - doRefUrl "opened url" ref - `andAnyway` doRefKey "opened key" ref - ) - `andAnyway` doSet "set2 value" (JE.string "hello") loc - `andAnyway` open (loc |> root) - `andAnyway` open (loc |> root |> parent) - `andAnyway` doSubscribe "query3 child added" (childAdded noOrder) loc - `andAnyway` doSubscribe "query4 child changed" (childChanged noOrder) loc - `andAnyway` doSubscribe "query5 child removed" (childRemoved noOrder) loc - `andAnyway` doSubscribe "query6 child moved" (childMoved noOrder) loc - `andAnyway` doSleep "2" 2 - `andAnyway` - doSet "set3 object value" - (JE.object [ ( "a", (JE.string "hello") ), ( "b", (JE.string "Elm") ) ]) - loc - `andAnyway` doSleep "3" 2 - `andAnyway` doSet "set4 add child" (JE.string "at Firebase") (loc |> sub "c") - `andAnyway` doSleep "4" 2 - `andAnyway` - (doSubscribe "subscribe" (valueChanged noOrder) loc - `andThen` \subscription -> doUnsubscribe "unsubscribe" subscription - ) - `andAnyway` doOnce "query once" (valueChanged noOrder) loc - `andAnyway` doRemove "remove child" (loc |> sub "b") - `andAnyway` - doUpdate "update object a and d" - (JE.object [ ( "a", (JE.string "Hello") ), ( "d", (JE.string "Elmies") ) ]) - loc - `andAnyway` - (open (loc |> sub "e" |> push) - `andThen` - \ref -> - doSet "push set" (JE.string <| key ref) (location ref) - `andThen` \ref -> doSetPriority "setPriority" (NumberPriority 17) (location ref) - ) - `andAnyway` succeed () diff --git a/demo/src/demo.html b/demo/src/demo.html deleted file mode 100644 index a72c85b..0000000 --- a/demo/src/demo.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - -ElmFire Demo - - - - - - - - \ No newline at end of file From a8f6e7ab45ae94bfadfc4761c7ca48f687b4b241 Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Tue, 20 Dec 2016 18:15:37 +0100 Subject: [PATCH 15/23] Generic EffectManager module. Subs by location. --- src/EffectManager.elm | 99 ++++++++++++++++++ src/ElmFire.elm | 215 +++++++++++++++++++++------------------ src/ElmFire/LowLevel.elm | 4 +- src/ElmFire/Types.elm | 6 +- test/src/Test.elm | 21 +++- 5 files changed, 239 insertions(+), 106 deletions(-) create mode 100644 src/EffectManager.elm diff --git a/src/EffectManager.elm b/src/EffectManager.elm new file mode 100644 index 0000000..444a20c --- /dev/null +++ b/src/EffectManager.elm @@ -0,0 +1,99 @@ +module EffectManager exposing (..) + +import Dict exposing (Dict) + + +{- -} + + +type alias CurrentSubs spec tagger handle = + Dict spec ( List tagger, handle ) + + +type alias RequestedSubs spec tagger = + Dict spec (List tagger) + + +type Alteration spec tagger handle + = Create spec (List tagger) + | Update spec handle (List tagger) + | Delete spec handle + + +requestedSubsFromList : + (mySub -> ( comparableSpec, tagger )) + -> List mySub + -> RequestedSubs comparableSpec tagger +requestedSubsFromList map list = + let + -- add : mySub -> Dict comparableSpec (List tagger) -> Dict comparableSpec (List tagger) + add mySub dictAccu = + let + ( spec, tagger ) = + map mySub + in + Dict.update + spec + (\maybeVal -> + Just <| + tagger + :: Maybe.withDefault [] maybeVal + ) + dictAccu + in + List.foldl add Dict.empty list + + +alterations : + RequestedSubs comparableSpec tagger + -> CurrentSubs comparableSpec tagger handle + -> List (Alteration comparableSpec tagger handle) +alterations requestedSubs currentSubs = + let + create spec requestedTaggers list = + Create spec requestedTaggers :: list + + update spec requestedTaggers ( currentTaggers, handle ) list = + Update spec handle requestedTaggers :: list + + delete spec ( currentTaggers, handle ) list = + Delete spec handle :: list + in + Dict.merge + create + update + delete + requestedSubs + currentSubs + [] + + +emptySubs : CurrentSubs comparableSpec tagger handle +emptySubs = + Dict.empty + + +insertSub : + comparableSpec + -> List tagger + -> handle + -> CurrentSubs comparableSpec tagger handle + -> CurrentSubs comparableSpec tagger handle +insertSub spec taggers handle = + Dict.insert spec ( taggers, handle ) + + +removeSub : + comparableSpec + -> CurrentSubs comparableSpec tagger handle + -> CurrentSubs comparableSpec tagger handle +removeSub spec = + Dict.remove spec + + +getSub : + comparableSpec + -> CurrentSubs comparableSpec tagger handle + -> Maybe ( List tagger, handle ) +getSub spec currentSubs = + Dict.get spec currentSubs diff --git a/src/ElmFire.elm b/src/ElmFire.elm index d5c23ce..a2d3765 100644 --- a/src/ElmFire.elm +++ b/src/ElmFire.elm @@ -10,88 +10,77 @@ First sketch of an ElmFire effect module. Simplifications: * Only valueChanged events * No query options -* Fixed Firebase location - -Notes: -* Doesn't spawn a process like most other effect modules do. Ok? @docs valueChanged -} --- import Dict -- import Process -- import Json.Decode as JD +-- import Dict exposing (Dict) import Task exposing (Task) +import ElmFire.Types exposing (..) import ElmFire.LowLevel as LL +import EffectManager as EM + + +-- Types + + +type alias Spec = + LocationSpec + + +type alias Tagger msg = + Result LL.Error LL.Snapshot -> msg + + +type alias Handle = + LL.Subscription + + +type alias State msg = + { currentSubs : EM.CurrentSubs Spec (Tagger msg) Handle + } + -- SUBSCRIPTIONS type MySub msg - = ValueChanged - -- Location - (Result LL.Error LL.Snapshot -> msg) + = ValueChanged LocationSpec (Tagger msg) subMap : (a -> b) -> MySub a -> MySub b subMap func sub = case sub of - ValueChanged tagger -> - ValueChanged (tagger >> func) + ValueChanged location tagger -> + ValueChanged location (tagger >> func) {-| Subscribe to valueChanged ... -} valueChanged : - -- Location -> - (Result LL.Error LL.Snapshot -> msg) + LL.Location + -> (Result LL.Error LL.Snapshot -> msg) -> Sub msg -valueChanged tagger = - subscription (ValueChanged tagger) +valueChanged (Location locationSpec) tagger = + subscription (ValueChanged locationSpec tagger) -- MANAGER -type alias State msg = - { subs : SubsDict msg - } - - -type alias SubsDict msg = - -- Dict.Dict Location - Maybe (ValueChangedSubscription msg) - - -type alias ValueChangedSubscription msg = - { subscribers : List (Result LL.Error LL.Snapshot -> msg) - , lowLevelSubscription : LL.Subscription - } - - init : Task Never (State msg) init = Task.succeed - (State - -- Dict.empty - Nothing - ) - - -(&>) : Task x a -> Task x b -> Task x b -(&>) t1 t2 = - Task.andThen t1 (\_ -> t2) + (State EM.emptySubs) type SelfMsg - = NewSnapshot LL.Snapshot - - - --- NewLowLevelSub Subscription + = NewSnapshot LocationSpec LL.Snapshot onEffects : @@ -100,63 +89,87 @@ onEffects : -> State msg -> Task Never (State msg) onEffects router mySubs state = - case ( mySubs, state.subs ) of - ( [], Nothing ) -> - Task.succeed state - - ( [], Just { subscribers, lowLevelSubscription } ) -> - LL.unsubscribe lowLevelSubscription - |> Task.map - (\_ -> { subs = Nothing }) - |> (flip Task.onError) - (\llError -> - Task.succeed { subs = Nothing } - -- TODO: Handle error - ) - - ( _ :: _, Nothing ) -> - (LL.subscribe - (\snapshot -> Platform.sendToSelf router (NewSnapshot snapshot)) - (\cancellation -> Task.succeed ()) - -- TODO: Handle cancellation - (LL.valueChanged LL.noOrder) - (LL.fromUrl "https://elmfiretest.firebaseio.com/test") - ) - |> Task.map - (\lowLevelSubscription -> - { subs = - Just - { subscribers = buildSubscriberList mySubs - , lowLevelSubscription = lowLevelSubscription + let + requestedSubs = + EM.requestedSubsFromList + (\(ValueChanged spec tagger) -> ( spec, tagger )) + mySubs + + alterations = + EM.alterations requestedSubs state.currentSubs + + onAlteration : + EM.Alteration LocationSpec (Tagger msg) LL.Subscription + -> State msg + -> Task Never (State msg) + onAlteration alteration state = + case alteration of + EM.Create locationSpec taggers -> + LL.subscribe + (\snapshot -> Platform.sendToSelf router (NewSnapshot locationSpec snapshot)) + (\cancellation -> Task.succeed ()) + -- TODO: Handle cancellation + (LL.valueChanged LL.noOrder) + (Location locationSpec) + |> Task.map + (\lowLevelSub -> + { currentSubs = + EM.insertSub locationSpec taggers lowLevelSub state.currentSubs } + ) + |> (flip Task.onError) + (\llError -> + Task.succeed state + -- TODO: Handle error + ) + + EM.Update locationSpec lowLevelSub taggers -> + Task.succeed + { currentSubs = + EM.insertSub locationSpec taggers lowLevelSub state.currentSubs } - ) - |> (flip Task.onError) - (\llError -> - Task.succeed { subs = Nothing } - -- TODO: Handle error - ) - - ( _ :: _, Just { subscribers, lowLevelSubscription } ) -> - Task.succeed - { subs = - (Just - { subscribers = buildSubscriberList mySubs - , lowLevelSubscription = lowLevelSubscription - } - ) - } + + EM.Delete locationSpec lowLevelSub -> + LL.unsubscribe lowLevelSub + |> Task.map + (\_ -> + { currentSubs = + EM.removeSub locationSpec state.currentSubs + } + ) + |> (flip Task.onError) + (\llError -> + Task.succeed state + -- TODO: Handle error + ) + in + alterations + |> chain onAlteration state + + + +{- + chain_Recursive : (a -> b -> Task x b) -> b -> List a -> Task x b + chain_Recursive stepTask start list = + case list of + [] -> + Task.succeed start + + elem :: rest -> + (stepTask elem start) + `Task.andThen` \stepResult -> chain stepTask stepResult rest +-} -buildSubscriberList : List (MySub msg) -> List (Result LL.Error LL.Snapshot -> msg) -buildSubscriberList mySubs = - List.map - (\mySub -> - case mySub of - ValueChanged tagger -> - tagger +chain : (a -> b -> Task x b) -> b -> List a -> Task x b +chain step start list = + List.foldl + (\elem intermediateTask -> + intermediateTask + `Task.andThen` step elem ) - mySubs + (Task.succeed start) + list onSelfMsg : @@ -164,13 +177,13 @@ onSelfMsg : -> SelfMsg -> State msg -> Task Never (State msg) -onSelfMsg router selfMsg state = - case ( selfMsg, state.subs ) of - ( _, Nothing ) -> +onSelfMsg router (NewSnapshot locationSpec snapshot) state = + case EM.getSub locationSpec state.currentSubs of + Nothing -> Task.succeed state - ( NewSnapshot snapshot, Just { subscribers, lowLevelSubscription } ) -> - subscribers + Just ( taggers, lowLevelSub ) -> + taggers |> List.map (\tagger -> Platform.sendToApp router (tagger (Ok snapshot))) |> Task.sequence |> Task.map (\_ -> state) diff --git a/src/ElmFire/LowLevel.elm b/src/ElmFire/LowLevel.elm index 6e673e2..cd3b8ad 100644 --- a/src/ElmFire/LowLevel.elm +++ b/src/ElmFire/LowLevel.elm @@ -181,8 +181,8 @@ A location can be constructed or obtained from Locations are generally unvalidated until their use in a task. The constructor functions are pure. -} -type Location - = Location LocationSpec +type alias Location = + ElmFire.Types.Location diff --git a/src/ElmFire/Types.elm b/src/ElmFire/Types.elm index ad6245d..e456f72 100644 --- a/src/ElmFire/Types.elm +++ b/src/ElmFire/Types.elm @@ -1,4 +1,4 @@ -module ElmFire.Types exposing (LocationSpec) +module ElmFire.Types exposing (..) {- Internal representation of locations @@ -12,3 +12,7 @@ module ElmFire.Types exposing (LocationSpec) type alias LocationSpec = List ( String, String ) + + +type Location + = Location LocationSpec diff --git a/test/src/Test.elm b/test/src/Test.elm index 6df0d73..fa832a2 100644 --- a/test/src/Test.elm +++ b/test/src/Test.elm @@ -57,6 +57,7 @@ type LogEntry = LogString String | LogStep String | LogSnapshot LL.Snapshot + | LogResponse Response type Action @@ -216,7 +217,7 @@ update action model = SubResponse response -> model - |> display (LogString "got subscription response: (...TODO) ") + |> display (LogResponse response) |> noticeResponse response Timeout timeoutStep -> @@ -261,7 +262,7 @@ updateStep step model = ) Subscribe -> - { model | subs = [ ElmFire.valueChanged (SubResponse << ValueChanged) ] } + { model | subs = [ ElmFire.valueChanged testLocation (SubResponse << ValueChanged) ] } |> display (LogStep "Subscribing to valueChanges") |> await 4000 1 responseIsValueChanged Timeout1 CheckResponse1 @@ -408,6 +409,9 @@ viewLogEntry entry = LogSnapshot snapshot -> viewSnapshot snapshot + LogResponse response -> + viewResponse response + viewSnapshot : LL.Snapshot -> Html Action viewSnapshot { key, existing, value, prevKey, priority } = @@ -433,6 +437,19 @@ viewSnapshot { key, existing, value, prevKey, priority } = ] +viewResponse : Response -> Html Action +viewResponse (ValueChanged result) = + div [] + [ text "Response" + , case result of + Err error -> + viewError error + + Ok snapshot -> + viewSnapshot snapshot + ] + + viewError : LL.Error -> Html Action viewError { description } = text description From 443de82402d941d547b58578032e8ff7233b50f8 Mon Sep 17 00:00:00 2001 From: Rogerio Chaves Date: Mon, 13 Feb 2017 08:32:49 -0200 Subject: [PATCH 16/23] Add example of reacting to value change --- example/src/Example.elm | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/example/src/Example.elm b/example/src/Example.elm index 4c53f10..bbf7df2 100644 --- a/example/src/Example.elm +++ b/example/src/Example.elm @@ -29,6 +29,7 @@ import ElmFire.LowLevel , Subscription , Error ) +import ElmFire -- Firebase location to access: @@ -37,9 +38,10 @@ import ElmFire.LowLevel firebaseUrl : String firebaseUrl = - "https://elmfire.firebaseio-demo.com/example" + "https://elmfire-test.firebaseio.com/example" +main : Program Never main = Html.App.program { init = init @@ -50,7 +52,7 @@ main = type alias Model = - () + String @@ -60,11 +62,12 @@ type alias Model = type Msg = Send String | Sent (Result Error ()) + | ValueChanged (Result Error Snapshot) init : ( Model, Cmd Msg ) init = - ( () + ( "" , Cmd.none ) @@ -87,19 +90,35 @@ update msg model = in ( model, Cmd.none ) + ValueChanged result -> + case result of + Ok snapshot -> + ( toString (snapshot.value), Cmd.none ) + + Err _ -> + ( model, Cmd.none ) + subscriptions : Model -> Sub Msg subscriptions model = - Sub.none + ElmFire.valueChanged (fromUrl firebaseUrl) ValueChanged view : Model -> Html Msg view model = div [] - [ label [] - [ text "Set value: " - , input - [ on "input" (JD.map Send targetValue) ] - [] + [ text "ElmFire test at: " + , a [ href firebaseUrl, target "_blank" ] [ text firebaseUrl ] + , div [] + [ label [] + [ text "set value: " + , input [ on "input" (JD.map Send targetValue) ] [] + ] + ] + , div [] + [ label [] + [ text "query result: " + , output [] [ text model ] + ] ] ] From ba217e54831e1e4b07e33ec6b6f5835cf9563837 Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Sun, 5 Mar 2017 17:35:57 +0100 Subject: [PATCH 17/23] Use my Firebase project "elmfiretest" for example --- example/src/Example.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/Example.elm b/example/src/Example.elm index bbf7df2..61ba3aa 100644 --- a/example/src/Example.elm +++ b/example/src/Example.elm @@ -38,7 +38,7 @@ import ElmFire firebaseUrl : String firebaseUrl = - "https://elmfire-test.firebaseio.com/example" + "https://elmfiretest.firebaseio.com/example" main : Program Never From 985cc52a72d0f167a16a77a3c802d777586f987e Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Sun, 5 Mar 2017 18:00:34 +0100 Subject: [PATCH 18/23] Example uses user defined html file and modd instead of Makefile. --- example/Makefile | 45 --------------------------------------------- example/index.html | 18 ++++++++++++++++++ example/modd.conf | 8 ++++++++ 3 files changed, 26 insertions(+), 45 deletions(-) delete mode 100644 example/Makefile create mode 100644 example/index.html create mode 100644 example/modd.conf diff --git a/example/Makefile b/example/Makefile deleted file mode 100644 index 353b416..0000000 --- a/example/Makefile +++ /dev/null @@ -1,45 +0,0 @@ - -ELM_MAIN = Example -ELM_OUTPUT = example.html -STATIC = -BROWSER_TARGET = example.html - -SOURCE_DIR = src -BUILD_DIR = build - -### - -ELM_SOURCE = $(SOURCE_DIR)/$(ELM_MAIN).elm -ELM_BUILD = $(BUILD_DIR)/$(ELM_OUTPUT) -STATIC_SOURCES = $(STATIC:%=$(SOURCE_DIR)/%) - -### - -.PHONY: all open clean clean-build clean-all static elm new - -all: $(BUILD_DIR) $(ELM_BUILD) static - -new: clean-build all - -$(ELM_BUILD): elm - -elm: - elm make --yes --output $(ELM_BUILD) $(ELM_SOURCE) - -open: - xdg-open $(BUILD_DIR)/$(BROWSER_TARGET) 2>/dev/null - -static: # : $(BUILD_DIR)/% : $(SOURCE_DIR)/% $(BUILD_DIR) - rsync -rpE --ignore-missing-args stopgap $(STATIC_SOURCES) $(BUILD_DIR) - -$(BUILD_DIR): - mkdir $(BUILD_DIR) - -clean: - rm -rf elm-stuff - -clean-build: - rm -rf $(BUILD_DIR) - rm -rf elm-stuff/build-artifacts - -clean-all: clean-build clean diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000..aaaf500 --- /dev/null +++ b/example/index.html @@ -0,0 +1,18 @@ + + + + + ElmFire Example + + +
+ + + + + + + diff --git a/example/modd.conf b/example/modd.conf new file mode 100644 index 0000000..571931c --- /dev/null +++ b/example/modd.conf @@ -0,0 +1,8 @@ + +../src/**/*.elm ../src/**/Native/*.js src/**/*.elm { + prep: elm make --warn --yes --output build/elm.js src/Example.elm +} + +index.html build/elm.js { + daemon: devd -mqo . +} From 899551a45d62e563a0c973ebfc5521558f81811a Mon Sep 17 00:00:00 2001 From: Thomas Weiser Date: Sun, 5 Mar 2017 18:23:36 +0100 Subject: [PATCH 19/23] Don't include firebase.js. App needs to load it beforehand. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As suggested by Rogério Chaves: https://github.com/ThomasWeiser/elmfire/pull/15#issue-207484449 Pros: - No need to patch Firebase script - Faster loading from CDN / cache / parallel chunk. - Can upgrade firebase for non-breaking changes without changing ElmFire. - More flexibility while developing new ElmFire version Cons: - ElmFire is not self-contained any more. - User is now responsibly for compatibility of the version of included firebase.js script. This is a preliminary change during development. We may change back to included Firebase script for published versions of ElmFire. Needs to be discussed with community. --- example/index.html | 1 + src/ElmFire/Auth/LowLevel.elm | 1 - src/ElmFire/LowLevel.elm | 1 - src/Native/Firebase.js | 279 ---------------------------------- test/index.html | 1 + 5 files changed, 2 insertions(+), 281 deletions(-) delete mode 100644 src/Native/Firebase.js diff --git a/example/index.html b/example/index.html index aaaf500..d5ee63c 100644 --- a/example/index.html +++ b/example/index.html @@ -6,6 +6,7 @@
+