Compare commits

..
Author SHA1 Message Date
yevh-berdnyk ba74308139 Merge remote-tracking branch 'origin/tests/removing-rerun-errors' into tests/removing-rerun-errors 2021-12-17 17:03:21 +02:00
yevh-berdnyk a8fd6fdd60 2 rerun errors removed 2021-12-17 17:01:45 +02:00
yevh-berdnyk fe9dd7c549 Merge branch 'tests/removing-rerun-errors' of github.com:status-im/status-react into tests/removing-rerun-errors 2021-12-17 16:59:54 +02:00
Churikova Tetiana be79a8e54c e2e: fix
Signed-off-by: Churikova Tetiana <churikova.tm@gmail.com>
2021-12-17 14:48:24 +01:00
Roman Volosovskyi 36d96c6264 [#12912] Fix endless loading after removing messages in group chat 2021-12-17 12:44:08 +02:00
jinhojang6 340213ece5 Update translations
Signed-off-by: Shivek Khurana <shivek@status.im>
2021-12-17 15:54:06 +05:30
Churikova Tetiana 032a8786ff e2e: fixes
Signed-off-by: Churikova Tetiana <churikova.tm@gmail.com>
2021-12-16 17:46:48 +01:00
Michele Balistreri 15d671275f avoid closing/opening NFC dialog in rapid sequence
Signed-off-by: Michele Balistreri <michele@bitgamma.com>
2021-12-16 18:32:08 +03:00
Ajay Sivan f3533c5f99 Show message timestamps on tap, remove from bubbles
Signed-off-by: andrey <motor4ik@gmail.com>
2021-12-16 13:15:35 +01:00
yevh-berdnyk b18d7b6a2a e2e: setup and teardown with shared drivers 2021-12-15 21:14:16 +02:00
Roman Volosovskyi 2c23ab4c5d [#12867] Add accessibility IDs to toggles for e2e 2021-12-15 18:57:25 +02:00
Roman Volosovskyi 646add59b8 [#12782] Re-open app on the last open chat 2021-12-15 13:45:42 +02:00
yevh-berdnyk 71651601ad Removed rerun errors 2021-12-15 03:55:27 +02:00
dependabot[bot] cd111aeeec Bump lxml from 4.6.2 to 4.6.5 in /test/appium
Bumps [lxml](https://github.com/lxml/lxml) from 4.6.2 to 4.6.5.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-4.6.2...lxml-4.6.5)

---
updated-dependencies:
- dependency-name: lxml
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-13 19:45:32 +00:00
Roman Volosovskyi 81b60ce86c [#12907] Fix error on 'Accept and join' in referral chat 2021-12-13 16:42:08 +02:00
Roman Volosovskyi 356dd89147 [#12898] Fix endless loading in pubchats 2021-12-13 13:53:09 +02:00
Roman Volosovskyi a6102c1133 Fix unique :key warning on add pubchat screen 2021-12-13 12:59:03 +02:00
Andrea Maria Piana 7dfa93f2a2 Fix syncing public chats 2021-12-10 17:55:12 +00:00
Michele Balistreri c6271557b1 fix pairing for new cards
Signed-off-by: Michele Balistreri <michele@bitgamma.com>
2021-12-10 17:45:27 +03:00
Shivek Khurana d1f3a33194 Add 1.18 to RELEASES.MD
Signed-off-by: Shivek Khurana <shivek@status.im>
2021-12-09 15:46:55 +05:30
Shivek Khurana c6371bf3b3 Bump release to 1.18.0
Signed-off-by: Shivek Khurana <shivek@status.im>
2021-12-08 16:39:24 +05:30
35 changed files with 615 additions and 358 deletions
+35
View File
@@ -1,3 +1,38 @@
## 1.18.0 Release notes :package:
### Features
* 🚦Online status indicator <https://github.com/status-im/status-react/issues/12547>
* 👛 Add possibility to select wallet for ENS names <https://github.com/status-im/status-react/pull/12761>
* 🐞 Bug reporting and logs sharing improvements <https://github.com/status-im/status-react/pull/12773>
* 🍳 Simplify Keycard onboarding by using defaults for pairing password <https://github.com/status-im/status-react/issues/12685>
* 💾 Backup contacts via Waku <https://github.com/status-im/status-react/issues/12550>
* 🪙 Added ENS token <https://github.com/status-im/status-react/issues/12807>
* 🌠 Using react-native-fast-image for stickers <https://github.com/status-im/status-react/pull/12769>
#### Message reliability improvements
* 𝌗 Show loading until all messages are processed <https://github.com/status-im/status-react/pull/12834>
* 💪🏼 More robust message confirmations handling <https://github.com/status-im/status-react/pull/12824>
* 📶 Allow sending messages when offline <https://github.com/status-im/status-react/issues/11889>
#### Improvements on device pairing
* 🔔💻 Notification in activity centre now sync across devices <https://github.com/status-im/status-react/issues/12571>
* ✅💻 Sync read messages across devices <https://github.com/status-im/status-react/pull/12698>
#### Network settings improvements
* ✨ Add Binance Smart chain <https://github.com/status-im/status-react/pull/12809>
* ♏︎ Added custom network symbol <https://github.com/status-im/status-react/commit/c16b0860ec921a61c119857de27c6f29e301e4f6>
* 💰 Network fee on xDai chain <https://github.com/status-im/status-react/pull/12845>
* 📇 Support tx history (in a way) when on BSC chains <https://github.com/status-im/status-react/pull/12843>
* 🙅🏻 Remove POA network from the list <https://github.com/status-im/status-react/pull/12887>
### Bug Fixes
* Fix activity center community invite preview <https://github.com/status-im/status-react/pull/12803>
* Missing notifications for some contacts <https://github.com/status-im/status-react/issues/12776>
* Fixes scrolling issues on the 'Max priority fee' bottom sheet <https://github.com/status-im/status-react/issues/12530>
* Handle Keycard timeout on iOS 15 <https://github.com/status-im/status-react/pull/12826>
* Allow user to be reinvited to the same group after leaving <https://github.com/status-im/status-react/pull/12814>
* Fix for "TypeError: Network request failed" when switching to Goerli network <https://github.com/status-im/status-react/issues/11964>
* Fix for Spin of death on adding custom NFT token contract <https://github.com/status-im/status-react/issues/12365>
* Changing screen orientation results in breaking images / layout <https://github.com/status-im/status-react/issues/12858>
## 1.17
### For iOS and Android
+1 -1
View File
@@ -1 +1 @@
1.18.2
1.18.0
+1 -1
View File
@@ -1,7 +1,7 @@
library 'status-jenkins-lib@v1.3.3'
pipeline {
agent { label 'linux && x86_64 && nix-2.3' }
agent { label 'linux' }
options {
timestamps()
+1 -1
View File
@@ -1,7 +1,7 @@
library 'status-jenkins-lib@v1.3.3'
pipeline {
agent { label 'macos && x86_64 && nix-2.3 && xcode-12.5' }
agent { label 'macos && x86_64 && xcode-12.5' }
parameters {
string(
+1 -1
View File
@@ -9,7 +9,7 @@
(def key->string str)
(defn- set-item! [key value]
(defn set-item! [key value]
(-> ^js async-storage
(.setItem (key->string key)
(clj->transit value))
-1
View File
@@ -169,7 +169,6 @@
(let [{:keys [custom-domain? username address]}
(:ens/registration db)
{:keys [public-key]} (:multiaccount db)
address (or address (ethereum/default-address db))
chain (ethereum/chain-keyword db)
chain-id (ethereum/chain-id db)
amount (registration-cost chain-id)
+68 -53
View File
@@ -1,65 +1,64 @@
(ns status-im.events
(:require [re-frame.core :as re-frame]
[status-im.chat.models :as chat]
[status-im.i18n.i18n :as i18n]
[status-im.mailserver.core :as mailserver]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.react :as react]
[status-im.utils.fx :as fx]
status-im.utils.logging.core
status-im.backup.core
[status-im.wallet.core :as wallet]
[status-im.keycard.core :as keycard]
[status-im.utils.dimensions :as dimensions]
[status-im.multiaccounts.biometric.core :as biometric]
[status-im.constants :as constants]
[status-im.native-module.core :as status]
[status-im.ui.components.permissions :as permissions]
[status-im.utils.utils :as utils]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.anon-metrics.core :as anon-metrics]
[status-im.utils.universal-links.core :as universal-links]
clojure.set
status-im.currency.core
status-im.navigation
status-im.utils.universal-links.core
status-im.wallet.custom-tokens.core
status-im.waku.core
status-im.wallet.choose-recipient.core
status-im.wallet.accounts.core
status-im.popover.core
status-im.visibility-status-popover.core
status-im.visibility-status-updates.core
status-im.bottom-sheet.core
(:require clojure.set
[re-frame.core :as re-frame]
status-im.add-new.core
status-im.search.core
status-im.http.core
status-im.profile.core
[status-im.anon-metrics.core :as anon-metrics]
[status-im.async-storage.core :as async-storage]
status-im.backup.core
status-im.bootnodes.core
status-im.bottom-sheet.core
status-im.browser.core
status-im.browser.permissions
[status-im.chat.models :as chat]
status-im.chat.models.images
status-im.ui.screens.privacy-and-security-settings.events
status-im.chat.models.input
status-im.chat.models.loading
status-im.chat.models.transport
[status-im.constants :as constants]
status-im.contact.block
status-im.contact.chat
status-im.contact.core
status-im.currency.core
[status-im.ethereum.json-rpc :as json-rpc]
status-im.ethereum.subscriptions
status-im.fleet.core
status-im.http.core
[status-im.i18n.i18n :as i18n]
status-im.init.core
[status-im.keycard.core :as keycard]
status-im.log-level.core
status-im.mailserver.constants
[status-im.mailserver.core :as mailserver]
[status-im.multiaccounts.biometric.core :as biometric]
[status-im.multiaccounts.core :as multiaccounts]
status-im.multiaccounts.login.core
status-im.multiaccounts.logout.core
status-im.multiaccounts.update.core
[status-im.native-module.core :as status]
[status-im.navigation :as navigation]
status-im.notifications-center.core
status-im.pairing.core
status-im.popover.core
status-im.profile.core
status-im.search.core
status-im.signals.core
status-im.stickers.core
status-im.transport.core
status-im.init.core
status-im.log-level.core
status-im.mailserver.constants
status-im.ethereum.subscriptions
status-im.fleet.core
status-im.contact.block
status-im.contact.core
status-im.contact.chat
status-im.chat.models.input
status-im.chat.models.loading
status-im.bootnodes.core
status-im.browser.core
status-im.browser.permissions
status-im.chat.models.transport
status-im.notifications-center.core
[status-im.navigation :as navigation]))
[status-im.ui.components.permissions :as permissions]
[status-im.ui.components.react :as react]
status-im.ui.screens.privacy-and-security-settings.events
[status-im.utils.dimensions :as dimensions]
[status-im.utils.fx :as fx]
status-im.utils.logging.core
[status-im.utils.universal-links.core :as universal-links]
[status-im.utils.utils :as utils]
status-im.visibility-status-popover.core
status-im.visibility-status-updates.core
status-im.waku.core
status-im.wallet.accounts.core
status-im.wallet.choose-recipient.core
[status-im.wallet.core :as wallet]
status-im.wallet.custom-tokens.core))
(re-frame/reg-fx
:dismiss-keyboard
@@ -193,8 +192,24 @@
(fx/defn on-will-focus
{:events [:screens/on-will-focus]
:interceptors [anon-metrics/interceptor]}
[cofx view-id]
[{:keys [db] :as cofx} view-id]
(fx/merge cofx
(cond
(= :chat view-id)
{::async-storage/set! {:chat-id (get-in cofx [:db :current-chat-id])
:key-uid (get-in cofx [:db :multiaccount :key-uid])}
:db (assoc db :screens/was-focused-once? true)}
(= :login view-id)
{}
(not (get db :screens/was-focused-once?))
{:db (assoc db :screens/was-focused-once? true)}
:else
{::async-storage/set! {:chat-id nil
:key-uid nil}
:db (assoc db :screens/was-focused-once? true)})
#(case view-id
:keycard-settings (keycard/settings-screen-did-load %)
:reset-card (keycard/reset-card-screen-did-load %)
+16 -2
View File
@@ -325,6 +325,19 @@
(logging/set-log-level (:log-level multiaccount))
(notifications-center/get-activity-center-notifications-count))))
(re-frame/reg-fx
::open-last-chat
(fn [key-uid]
(async-storage/get-item
:chat-id
(fn [chat-id]
(when chat-id
(async-storage/get-item
:key-uid
(fn [stored-key-uid]
(when (= stored-key-uid key-uid)
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])))))))))
(fx/defn get-chats-callback
{:events [::get-chats-callback]}
[{:keys [db] :as cofx}]
@@ -339,7 +352,8 @@
::initialize-wallet
(fn [accounts custom-tokens favourites]
(re-frame/dispatch [::initialize-wallet
accounts custom-tokens favourites]))}
accounts custom-tokens favourites]))
::open-last-chat (get-in db [:multiaccount :key-uid])}
notifications-enabled?
(assoc ::notifications/enable nil))
(transport/start-messenger)
@@ -381,7 +395,7 @@
config/metrics-enabled?)
(navigation/navigate-to :anon-metrics-opt-in {})
:else (re-frame/dispatch [:init-root :chat-stack]))))
:else (re-frame/dispatch [:init-root :chat-stack]))))
(fx/defn login-only-events
[{:keys [db] :as cofx} key-uid password save-password?]
+2 -2
View File
@@ -1,6 +1,6 @@
(ns status-im.navigation
(:require [status-im.utils.fx :as fx]
[status-im.anon-metrics.core :as anon-metrics]))
(:require [status-im.anon-metrics.core :as anon-metrics]
[status-im.utils.fx :as fx]))
(defn- all-screens-params [db view screen-params]
(cond-> db
+31 -11
View File
@@ -1,17 +1,18 @@
(ns status-im.navigation.core
(:require
[re-frame.core :as re-frame]
[status-im.ui.screens.views :as views]
[status-im.utils.platform :as platform]
[status-im.navigation.roots :as roots]
[status-im.ui.components.react :as react]
[quo.components.text-input :as quo.text-input]
[status-im.ui.components.icons.icons :as icons]
[quo.design-system.colors :as quo.colors]
[status-im.utils.fx :as fx]
["react-native-navigation" :refer (Navigation)]
["react-native" :as rn]
["react-native-gesture-handler" :refer (gestureHandlerRootHOC)]
["react-native" :as rn]))
["react-native-navigation" :refer (Navigation)]
[quo.components.text-input :as quo.text-input]
[quo.design-system.colors :as quo.colors]
[re-frame.core :as re-frame]
[status-im.navigation.roots :as roots]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im.ui.screens.views :as views]
[status-im.utils.fx :as fx]
[status-im.utils.platform :as platform]
[taoensso.timbre :as log]))
(def debug? ^boolean js/goog.DEBUG)
@@ -26,6 +27,7 @@
;; REGISTER COMPONENT (LAZY)
(defn reg-comp [key]
(log/debug "reg-comp" key)
(if-let [comp (get views/components (keyword key))]
(.registerComponent Navigation key (fn [] (views/component comp)))
(let [screen (views/screen key)]
@@ -35,6 +37,7 @@
(.setLazyComponentRegistrator Navigation reg-comp))
(defn dismiss-all-modals []
(log/debug "dissmiss-all-modals")
(when @curr-modal
(reset! curr-modal false)
(reset! dissmissing true)
@@ -44,6 +47,7 @@
;; PUSH SCREEN
(defn navigate [comp]
(log/debug "NAVIGATE" comp)
(let [{:keys [options]} (get views/screens comp)]
(.push Navigation
(name @root-comp-id)
@@ -57,6 +61,7 @@
;; OPEN MODAL
(defn update-modal-topbar-options [options]
(log/debug "update-modal-topbar-options" options)
(merge options
(roots/merge-top-bar {:elevation 0
:noBorder true
@@ -68,6 +73,7 @@
options)))
(defn open-modal [comp]
(log/debug "open-modal" comp)
(let [{:keys [options]} (get views/screens comp)]
(if @dissmissing
(reset! dissmissing comp)
@@ -88,6 +94,7 @@
;; DISSMISS MODAL
(defn dissmissModal []
(log/debug "dissmissModal")
(reset! dissmissing true)
(.dismissModal Navigation (name (last @modals))))
@@ -105,6 +112,7 @@
(handler)))))))
(defn set-view-id [view-id]
(log/debug "set-view-id" view-id)
(when-let [{:keys [on-focus]} (get views/screens view-id)]
(re-frame/dispatch [:set :view-id view-id])
(re-frame/dispatch [:screens/on-will-focus view-id])
@@ -135,6 +143,7 @@
(.events Navigation)
(fn [^js evn]
(let [view-id (keyword (.-componentName evn))]
(log/debug "screen-appear-reg" view-id)
(when (get views/screens view-id)
(when (and (not= view-id :bottom-sheet)
(not= view-id :popover)
@@ -159,6 +168,7 @@
(re-frame/reg-fx
:init-root-fx
(fn [new-root-id]
(log/debug :init-root-fx new-root-id)
(reset! root-comp-id new-root-id)
(reset! root-id @root-comp-id)
(.setRoot Navigation (clj->js (get (roots/roots) new-root-id)))))
@@ -166,6 +176,7 @@
(re-frame/reg-fx
:init-root-with-component-fx
(fn [[new-root-id new-root-comp-id]]
(log/debug :init-root-with-component-fx new-root-id new-root-comp-id)
(reset! root-comp-id new-root-comp-id)
(reset! root-id @root-comp-id)
(.setRoot Navigation (clj->js (get (roots/roots) new-root-id)))))
@@ -173,6 +184,7 @@
(fx/defn set-multiaccount-root
{:events [::set-multiaccount-root]}
[{:keys [db]}]
(log/debug :set-multiaccounts-root)
(let [key-uid (get-in db [:multiaccounts/login :key-uid])
keycard-account? (boolean (get-in db [:multiaccounts/multiaccounts
key-uid
@@ -192,6 +204,7 @@
(.hide ^js splash-screen))))
(defn get-screen-component [comp]
(log/debug :get-screen-component comp)
(let [{:keys [options]} (get views/screens comp)]
{:component {:id comp
:name comp
@@ -203,6 +216,7 @@
(re-frame/reg-fx
:set-stack-root-fx
(fn [[stack comp]]
(log/debug :set-stack-root-fx stack comp)
(.setStackRoot Navigation
(name stack)
(clj->js (if (vector? comp)
@@ -225,6 +239,7 @@
(re-frame/reg-fx
:change-tab-fx
(fn [tab]
(log/debug :change-tab-fx)
(reset! root-comp-id (get tab-root-ids (get tab-key-idx tab)))
(.mergeOptions Navigation "tabs-stack" (clj->js {:bottomTabs {:currentTabIndex (get tab-key-idx tab)}}))
;;when we change tab we want to dismiss all modals
@@ -234,6 +249,7 @@
(re-frame/reg-fx
:change-tab-count-fx
(fn [[tab cnt]]
(log/debug :change-tab-count-fx tab cnt)
(.mergeOptions Navigation
(name (get tab-root-ids (get tab-key-idx tab)))
(clj->js {:bottomTab (cond
@@ -253,6 +269,7 @@
(re-frame/reg-fx
:pop-to-root-tab-fx
(fn [comp]
(log/debug :pop-to-root-tab-fx comp)
(dismiss-all-modals)
(.popToRoot Navigation (name comp))))
@@ -343,11 +360,13 @@
(re-frame/reg-fx
:navigate-to-fx
(fn [key]
(log/debug :navigate-to-fx key)
(navigate key)))
(re-frame/reg-fx
:navigate-back-fx
(fn []
(log/debug :navigate-back-fx)
(if @curr-modal
(dissmissModal)
(.pop Navigation (name @root-comp-id)))))
@@ -355,5 +374,6 @@
(re-frame/reg-fx
:navigate-replace-fx
(fn [view-id]
(log/debug :navigate-replace-fx view-id)
(.pop Navigation (name @root-comp-id))
(navigate view-id)))
+3 -1
View File
@@ -1,5 +1,6 @@
(ns status-im.notifications.local
(:require [status-im.utils.fx :as fx]
(:require [status-im.async-storage.core :as async-storage]
[status-im.utils.fx :as fx]
[status-im.ethereum.decode :as decode]
["@react-native-community/push-notification-ios" :default pn-ios]
[status-im.notifications.android :as pn-android]
@@ -40,6 +41,7 @@
(defn handle-notification-press [{{deep-link :deepLink} :userInfo
interaction :userInteraction}]
(async-storage/set-item! (str :chat-id) nil)
(when (and deep-link
(or platform/ios?
(and platform/android? interaction)))
@@ -103,4 +103,5 @@
(conj chats lang-topic)
chats)]
(for [chat chats]
(render-topic chat)))]])]]))
^{:key chat}
[render-topic chat]))]])]]))
@@ -181,7 +181,7 @@
:message-id @current-player-message-id}))
nil)
(defview message-content [{:keys [audio audio-duration-ms message-id outgoing]} timestamp-view]
(defview message-content [{:keys [audio audio-duration-ms message-id outgoing]}]
(letsubs [state (reagent/atom nil)
progress (reagent/atom 0)
progress-anim (anim/create-value 0)
@@ -223,5 +223,4 @@
(#{:playing :paused :seeking} (:general @state)) @progress
:else (:duration @state))
s (quot time 1000)]
(gstring/format "%02d:%02d" (quot s 60) (mod s 60)))]
timestamp-view]]))))
(gstring/format "%02d:%02d" (quot s 60) (mod s 60)))]]]))))
+138 -104
View File
@@ -24,42 +24,64 @@
[status-im.ui.screens.chat.components.reply :as components.reply]
[status-im.ui.screens.chat.message.link-preview :as link-preview]
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im.ui.components.animation :as animation]
[status-im.chat.models.pin-message :as models.pin-message])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn message-timestamp-anim
[anim-opacity show-timestamp?]
(animation/start
(animation/anim-sequence
[(animation/timing
anim-opacity
{:toValue 1
:duration 100
:easing (.-ease ^js animation/easing)
:useNativeDriver true})
(animation/timing
anim-opacity
{:toValue 0
:delay 2000
:duration 100
:easing (.-ease ^js animation/easing)
:useNativeDriver true})]) #(reset! show-timestamp? false)))
(defview mention-element [from]
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
contact-name))
(def edited-at-text (str " ⌫ " (i18n/label :t/edited)))
(defn message-status [{:keys [outgoing content outgoing-status pinned edited-at in-popover?]}]
(when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover
[react/view
{:align-self :flex-end
:position :absolute
:bottom 9 ; 6 Bubble bottom, 3 message baseline
(if (:rtl? content) :left :right) 0
:flex-direction :row
:align-items :flex-end}
(when outgoing
[icons/icon (case outgoing-status
:sending :tiny-icons/tiny-pending
:sent :tiny-icons/tiny-sent
:not-sent :tiny-icons/tiny-warning
:delivered :tiny-icons/tiny-delivered
:tiny-icons/tiny-pending)
{:width 16
:height 12
:color (if pinned colors/gray colors/white)
:accessibility-label (name outgoing-status)}])
(when edited-at [react/text {:style (style/message-status-text (and outgoing (not pinned)))} edited-at-text])]))
(defn message-timestamp
([message]
[message-timestamp message false])
([{:keys [timestamp-str outgoing content outgoing-status pinned edited-at in-popover?]} justify-timestamp?]
(when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover
[react/view (when justify-timestamp?
{:align-self :flex-end
:position :absolute
:bottom 9 ; 6 Bubble bottom, 3 message baseline
(if (:rtl? content) :left :right) 0
:flex-direction :row
:align-items :flex-end})
(when (and outgoing justify-timestamp?)
[icons/icon (case outgoing-status
:sending :tiny-icons/tiny-pending
:sent :tiny-icons/tiny-sent
:not-sent :tiny-icons/tiny-warning
:delivered :tiny-icons/tiny-delivered
:tiny-icons/tiny-pending)
{:width 16
:height 12
:color (if pinned colors/gray colors/white)
:accessibility-label (name outgoing-status)}])
[react/text {:style (style/message-timestamp-text (and outgoing (not pinned)))}
(str
timestamp-str
(when edited-at edited-at-text))]])))
[{:keys [timestamp-str in-popover?] :as message} show-timestamp?]
(when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover
(let [anim-opacity (animation/create-value 0)]
[react/animated-view {:style (style/message-timestamp-wrapper message) :opacity anim-opacity}
(when @show-timestamp? (message-timestamp-anim anim-opacity show-timestamp?))
[react/text {:style (style/message-timestamp-text)}
timestamp-str]])))
(defview quoted-message
[_ {:keys [from parsed-text image]} outgoing current-public-key public? pinned]
@@ -168,19 +190,19 @@
(defn render-parsed-text [message tree]
(reduce (fn [acc e] (render-block message acc e)) [:<>] tree))
(defn render-parsed-text-with-timestamp [{:keys [timestamp-str outgoing edited-at in-popover?] :as message} tree]
(defn render-parsed-text-with-message-status [{:keys [outgoing edited-at in-popover?] :as message} tree]
(let [elements (render-parsed-text message tree)
timestamp [react/text {:style (style/message-timestamp-placeholder)}
(str (if (and outgoing (not in-popover?)) " " " ") (when-not in-popover? (str timestamp-str (when edited-at edited-at-text))))]
message-status [react/text {:style (style/message-status-placeholder)}
(str (if (and outgoing (not in-popover?)) " " " ") (when (and (not in-popover?) edited-at) edited-at-text))]
last-element (peek elements)]
;; Using `nth` here as slightly faster than `first`, roughly 30%
;; It's worth considering pure js structures for this code path as
;; it's perfomance critical
(if (= react/text-class (nth last-element 0))
;; Append timestamp to last text
(conj (pop elements) (conj last-element timestamp))
;; Append timestamp to new block
(conj elements timestamp))))
;; Append message status to last text
(conj (pop elements) (conj last-element message-status))
;; Append message status to new block
(conj elements message-status))))
(defn unknown-content-type
[{:keys [outgoing content-type content] :as message}]
@@ -409,7 +431,8 @@
(defn collapsible-text-message [{:keys [mentioned]} _]
(let [collapsed? (reagent/atom false)
collapsible? (reagent/atom false)]
collapsible? (reagent/atom false)
show-timestamp? (reagent/atom false)]
(fn [{:keys [content outgoing current-public-key public? pinned in-popover?] :as message} on-long-press modal]
(let [max-height (when-not (or outgoing modal)
(if @collapsible?
@@ -418,7 +441,8 @@
[react/touchable-highlight
(when-not modal
{:on-press (fn [_]
(react/dismiss-keyboard!))
(react/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press (fn []
(if @collapsed?
@@ -426,40 +450,41 @@
(js/setTimeout #(on-long-press-fn on-long-press message content) 200))
(on-long-press-fn on-long-press message content)))
:disabled in-popover?})
[react/view {:style (style/message-view message)}
[react/view {:style (style/message-view-content)
:max-height max-height}
(let [response-to (:response-to content)]
[react/view {:on-layout
#(when (and (> (.-nativeEvent.layout.height ^js %) max-message-height-px)
(not @collapsible?)
(not outgoing)
(not modal))
(reset! collapsed? true)
(reset! collapsible? true))}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned])
[render-parsed-text-with-timestamp message (:parsed-text content)]])
(when-not @collapsed?
[message-timestamp message true])
(when (and @collapsible? (not modal))
(if @collapsed?
(let [color (if pinned colors/pin-background (if mentioned colors/mentioned-background colors/blue-light))]
[react/touchable-highlight
{:on-press #(swap! collapsed? not)
:style {:position :absolute :bottom 0 :left 0 :right 0 :height 72}}
[react/linear-gradient {:colors [(str color "00") color]
:start {:x 0 :y 0} :end {:x 0 :y 0.9}}
[react/view {:height 72 :align-self :center :justify-content :flex-end
:padding-bottom 10}
[react/view (style/collapse-button)
[icons/icon :main-icons/dropdown
{:color colors/white}]]]]])
[react/touchable-highlight {:on-press #(swap! collapsed? not)
:style {:align-self :center :margin 5}}
[react/view (style/collapse-button)
[icons/icon :main-icons/dropdown-up
{:color colors/white}]]]))]]]))))
[react/view (style/message-view-wrapper outgoing)
[message-timestamp message show-timestamp?]
[react/view {:style (style/message-view message)}
[react/view {:style (style/message-view-content)
:max-height max-height}
(let [response-to (:response-to content)]
[react/view {:on-layout
#(when (and (> (.-nativeEvent.layout.height ^js %) max-message-height-px)
(not @collapsible?)
(not outgoing)
(not modal))
(reset! collapsed? true)
(reset! collapsible? true))}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned])
[render-parsed-text-with-message-status message (:parsed-text content)]])
(when-not @collapsible? [message-status message])
(when (and @collapsible? (not modal))
(if @collapsed?
(let [color (if pinned colors/pin-background (if mentioned colors/mentioned-background colors/blue-light))]
[react/touchable-highlight
{:on-press #(swap! collapsed? not)
:style {:position :absolute :bottom 0 :left 0 :right 0 :height 72}}
[react/linear-gradient {:colors [(str color "00") color]
:start {:x 0 :y 0} :end {:x 0 :y 0.9}}
[react/view {:height 72 :align-self :center :justify-content :flex-end
:padding-bottom 10}
[react/view (style/collapse-button)
[icons/icon :main-icons/dropdown
{:color colors/white}]]]]])
[react/touchable-highlight {:on-press #(swap! collapsed? not)
:style {:align-self :center :margin 5}}
[react/view (style/collapse-button)
[icons/icon :main-icons/dropdown-up
{:color colors/white}]]]))]]]]))))
(defmethod ->message constants/content-type-text
[message {:keys [on-long-press modal] :as reaction-picker}]
@@ -484,33 +509,37 @@
(defmethod ->message constants/content-type-emoji
[{:keys [content current-public-key outgoing public? pinned in-popover? message-pin-enabled] :as message} {:keys [on-long-press modal]
:as reaction-picker}]
(let [response-to (:response-to content)]
[message-content-wrapper message
[react/touchable-highlight (when-not modal
{:disabled in-popover?
:on-press (fn []
(react/dismiss-keyboard!))
:delay-long-press 100
:on-long-press (fn []
(on-long-press
(concat
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:label (i18n/label :t/message-reply)}
{:on-press #(react/copy-to-clipboard (get content :text))
:id :copy
:label (i18n/label :t/sharing-copy-to-clipboard)}]
(when message-pin-enabled [{:on-press #(pin-message message)
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))})
[react/view (style/message-view message)
[react/view {:style (style/message-view-content)}
[react/view {:style (style/style-message-text outgoing)}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned])
[react/text {:style (style/emoji-message message)}
(:text content)]]
[message-timestamp message]]]]
reaction-picker]))
(let [response-to (:response-to content)
show-timestamp? (reagent/atom false)]
(fn [] [message-content-wrapper message
[react/touchable-highlight (when-not modal
{:disabled in-popover?
:on-press (fn []
(react/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press (fn []
(on-long-press
(concat
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:label (i18n/label :t/message-reply)}
{:on-press #(react/copy-to-clipboard (get content :text))
:id :copy
:label (i18n/label :t/sharing-copy-to-clipboard)}]
(when message-pin-enabled [{:on-press #(pin-message message)
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))})
[react/view (style/message-view-wrapper outgoing)
[message-timestamp message show-timestamp?]
[react/view (style/message-view message)
[react/view {:style (style/message-view-content)}
[react/view {:style (style/style-message-text outgoing)}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned])
[react/text {:style (style/emoji-message message)}
(:text content)]]
[message-status message]]]]]
reaction-picker])))
(defmethod ->message constants/content-type-sticker
[{:keys [content from outgoing in-popover?]
@@ -556,14 +585,19 @@
(defmethod ->message constants/content-type-audio [message {:keys [on-long-press modal]
:as reaction-picker}]
[message-content-wrapper message
[react/touchable-highlight (when-not modal
{:on-long-press
(fn [] (on-long-press []))})
[react/view {:style (style/message-view message) :accessibility-label :audio-message}
[react/view {:style (style/message-view-content)}
[message.audio/message-content message [message-timestamp message false]]]]]
reaction-picker])
(let [show-timestamp? (reagent/atom false)]
(fn [] [message-content-wrapper message
[react/touchable-highlight (when-not modal
{:on-long-press
(fn [] (on-long-press []))
:on-press (fn []
(reset! show-timestamp? true))})
[react/view (style/message-view-wrapper (:outgoing message))
[message-timestamp message show-timestamp?]
[react/view {:style (style/message-view message) :accessibility-label :audio-message}
[react/view {:style (style/message-view-content)}
[message.audio/message-content message] [message-status message]]]]]
reaction-picker])))
(defmethod ->message :default [message]
[message-content-wrapper message
@@ -43,5 +43,5 @@
:justify-content :space-between})
(defn timestamp [outgoing]
(merge (message.style/message-timestamp-text outgoing)
(merge (message.style/audio-message-timestamp-text outgoing)
{:margin-left 40}))
@@ -29,11 +29,33 @@
(def message-timestamp
{:font-size 10})
(defn message-timestamp-placeholder
(defn message-status-placeholder
[]
(merge message-timestamp {:opacity 0 :color "rgba(0,0,0,0)"}))
(defn message-timestamp-text
(defn message-timestamp-wrapper [{:keys [last-in-group? outgoing group-chat]}]
{:justify-content :center
(if outgoing :margin-right :margin-left) 12 ;; horizontal margin is only required at the adjust side of the message.
:margin-top (if (and last-in-group?
(or outgoing
(not group-chat)))
16
0) ;; Add gap between message groups
:opacity 0})
(defn message-timestamp-text []
(merge message-timestamp
{:color colors/gray
:text-align :center}))
(defn message-status-text [outgoing]
{:font-size 10
:line-height 10
:color (if outgoing
colors/white-transparent-70-persist
colors/gray)})
(defn audio-message-timestamp-text
[outgoing]
(merge message-timestamp
{:line-height 10
@@ -44,7 +66,7 @@
(defn message-wrapper [{:keys [outgoing in-popover?]}]
(if (and outgoing (not in-popover?))
{:margin-left 96}
{:margin-right 52}))
{:margin-right 96}))
(defn message-author-wrapper
[outgoing display-photo? in-popover?]
@@ -156,10 +178,10 @@
:padding-left 3})
(defn emoji-message
[{:keys [incoming-group]}]
[{:keys [incoming-group outgoing]}]
{:font-size 28
:line-height 34 ;TODO: Smaller crops the icon on the top
:margin-right 12
:margin-right (if outgoing 18 0) ;; Margin to display outgoing message status
:margin-top (if incoming-group 4 0)})
(defn collapse-button []
@@ -171,6 +193,10 @@
:shadow-color (:shadow-01 @colors/theme)
:shadow-offset {:width 0 :height 4}})
(defn message-view-wrapper [outgoing]
{:align-self :flex-end
:flex-direction (if outgoing :row :row-reverse)})
(defn message-view
[{:keys [content-type outgoing group-chat last-in-group? mentioned pinned]}]
(merge
+11 -8
View File
@@ -51,11 +51,14 @@
(swap! handlers/handler-nesting-level inc))
(let [[first-arg & rest-args] args
initial-fxs? (map? first-arg)
fx-fns (if initial-fxs? rest-args args)]
(clojure.core/reduce (fn [fxs fx-fn]
(let [updated-cofx (update-db cofx fxs)]
(if fx-fn
(safe-merge fxs (fx-fn updated-cofx))
fxs)))
(if initial-fxs? first-arg {:db db})
fx-fns)))
fx-fns (if initial-fxs? rest-args args)
res
(clojure.core/reduce (fn [fxs fx-fn]
(let [updated-cofx (update-db cofx fxs)]
(if fx-fn
(safe-merge fxs (fx-fn updated-cofx))
fxs)))
(if initial-fxs? first-arg {:db db})
fx-fns)]
(swap! handlers/handler-nesting-level dec)
res))
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v0.92.4+hotfix.2",
"commit-sha1": "3df0ea59a83426b3b54049b3d56df459b0c77111",
"src-sha256": "1a63qlvvfzp9pkdaq7plhmadn0q9dm20blp1322q1piajphf641v"
"version": "v0.92.5",
"commit-sha1": "64589fbd1f81022c7f995a7d993f0c026ecc6923",
"src-sha256": "0d4zh9kmgi0vwgn1wsbjfnp7hiv2m0l1472wkjg23snskz2g5g14"
}
+1 -1
View File
@@ -1,6 +1,6 @@
[pytest]
norecursedirs = .git views
addopts = -s -v --tb=line --junitxml=result.xml
addopts = -s -v --tb=line --junitxml=result.xml --dist loadgroup -n 5
junit_family=legacy
markers =
critical: TCs by priority
+3 -3
View File
@@ -17,7 +17,7 @@ future==0.18.2
flaky==3.7.0
idna==2.10
kiwisolver==1.3.1
lxml==4.6.2
lxml==4.6.5
matplotlib==3.3.3
multidict==5.0.2
namedlist==1.8
@@ -30,9 +30,9 @@ pycryptodome==3.9.9
pyethash==0.1.27
pyparsing==2.4.7
pysha3==1.0.2
pytest==6.1.2
pytest==6.2.0
pytest-forked==1.3.0
pytest-xdist==2.2.0
pytest-xdist==2.5.0
python-dateutil==2.8.1
pytz==2020.4
PyYAML==5.4
+2 -2
View File
@@ -12,14 +12,14 @@ RERUN_ERRORS = [
'failed to start the browser or device',
'ERROR The test with session id',
"503 Service Unavailable",
"object has no attribute",
# "object has no attribute",
"[Errno 104] Connection reset by peer",
"Sauce could not start your job",
"HTTP Error 303",
"http.client.RemoteDisconnected: Remote end closed connection without response",
"[Errno 110] Connection timed out",
"replacement transaction underpriced",
"StaleElementReferenceException",
# "StaleElementReferenceException",
"'GetStartedButton' is not found on the screen",
"'AccessKeyButton' is not found on the screen",
"'SignInPhraseText' is not found on the screen"
@@ -1036,7 +1036,7 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase):
profile_1.mail_server_button.click()
profile_1.use_history_node_button.click()
profile_1.home_button.click(desired_view='chat')
if not public_chat_1.chat_element_by_text(message).is_element_displayed(30):
if not public_chat_1.chat_element_by_text(message).is_element_displayed(60):
self.errors.append('History was not fetched after enabling use_history_node')
self.errors.verify_no_errors()
@@ -1287,6 +1287,7 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase):
if not channel_2.chat_element_by_text(message_after_sync).is_element_displayed(30):
self.errors.append("Message sent to community channel after sync is not shown!")
[channel.back_button.click() for channel in (channel_1, channel_2)]
[home.get_chat(comm_before_sync_name, community=True).click() for home in (home_1, home_2)]
comm_before_1.add_channel(channel_after_sync)
if not comm_before_2.get_chat(channel_after_sync).is_element_displayed(30):
self.errors.append("New added channel after sync is not shown!")
@@ -616,7 +616,6 @@ class TestChatManagementMultipleDevice(MultipleDeviceTestCase):
device_1.just_fyi('check that new messages from blocked user are not delivered')
self.drivers[0].launch_app()
device_1.sign_in()
home_1.join_public_chat(chat_name)
for message in message_before_block_2, message_after_block_2:
if chat_public_1.chat_element_by_text(message).is_element_displayed():
self.errors.append(
@@ -1019,7 +1018,7 @@ class TestChatManagementMultipleDevice(MultipleDeviceTestCase):
public_1.chat_message_input.send_keys('@')
if (chat_1.search_user_in_mention_suggestion_list(ens_nickname_value).is_element_displayed() or
chat_1.search_user_in_mention_suggestion_list(sender['username']).is_element_displayed()):
self.errors.append('Blcoked user is available in mention suggestion list')
self.errors.append('Blocked user is available in mention suggestion list')
self.errors.verify_no_errors()
@@ -11,7 +11,7 @@ class TestCommandsMultipleDevices(MultipleDeviceTestCase):
@marks.critical
@marks.transaction
def test_send_eth_in_1_1_chat_transaction_push(self):
sender = transaction_senders['A']
sender = transaction_senders['B']
self.create_drivers(2)
device_1, device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
home_1 = device_1.recover_access(passphrase=sender['passphrase'], enable_notifications=True)
@@ -208,7 +208,7 @@ class TestMessagesOneToOneChatMultiple(MultipleDeviceTestCase):
message_before_edit, message_after_edit = "Message BEFORE edit 1", "Message AFTER edit 2"
public_chat_1.send_message(message_before_edit)
public_chat_1.edit_message_in_chat(message_before_edit, message_after_edit)
if not public_chat_1.element_by_text_part("⌫ Edited").is_element_displayed():
if not public_chat_1.element_by_text_part("⌫ Edited").is_element_displayed(60):
self.errors.append('No mark in message bubble about this message was edited')
device_2.just_fyi(
@@ -223,9 +223,9 @@ class TestMessagesOneToOneChatMultiple(MultipleDeviceTestCase):
if not home_1.element_by_text_part(message_after_edit_1_1).is_element_present():
self.errors.append('UNedited message version displayed on preview')
chat_private_1 = device_1_one_to_one_chat_element.click()
if not home_1.element_by_text_part(message_after_edit_1_1).is_element_present():
if not home_1.element_by_text_part(message_after_edit_1_1).is_element_present(30):
self.errors.append('No edited message in 1-1 chat displayed')
if not home_1.element_by_text_part("⌫ Edited").is_element_present():
if not home_1.element_by_text_part("⌫ Edited").is_element_present(30):
self.errors.append('No mark in message bubble about this message was edited on receiver side')
device_2.just_fyi("Verify Device1 can not edit and delete received message from Device2")
@@ -238,10 +238,10 @@ class TestMessagesOneToOneChatMultiple(MultipleDeviceTestCase):
device_2.just_fyi("Delete message and check it is not shown in chat preview on home")
chat_private_2.delete_message_in_chat(message_after_edit_1_1)
for chat in (chat_private_2, chat_private_1):
if chat.chat_element_by_text(message_after_edit_1_1).is_element_displayed():
if chat.chat_element_by_text(message_after_edit_1_1).is_element_displayed(30):
self.errors.append("Deleted message is shown in chat view for 1-1 chat")
chat_private_1.home_button.double_click()
if home_1.element_by_text(message_after_edit_1_1).is_element_displayed():
if home_1.element_by_text(message_after_edit_1_1).is_element_displayed(30):
self.errors.append("Deleted message is shown on chat element on home screen")
device_2.just_fyi("Send one more message and check that PN will be deleted with message deletion")
@@ -258,13 +258,13 @@ class TestMessagesOneToOneChatMultiple(MultipleDeviceTestCase):
chat_private_2.just_fyi("Check for that edited message is shown for Device 2 and delete message in public chat")
[home.home_button.double_click() for home in (home_1, home_2)]
public_chat_1, public_chat_2 = home_1.get_chat('#%s' % chat_name).click(), home_2.join_public_chat(chat_name)
if not public_chat_2.element_by_text_part("⌫ Edited").is_element_displayed():
if not public_chat_2.element_by_text_part("⌫ Edited").is_element_displayed(60):
self.errors.append('No mark in message bubble about this message was edited')
if not public_chat_2.element_by_text_part(message_after_edit).is_element_displayed():
if not public_chat_2.element_by_text_part(message_after_edit).is_element_displayed(60):
self.errors.append('Message is not edited.')
public_chat_1.delete_message_in_chat(message_after_edit)
for chat in (public_chat_1, public_chat_2):
if chat.chat_element_by_text(message_after_edit).is_element_displayed():
if chat.chat_element_by_text(message_after_edit).is_element_displayed(30):
self.errors.append("Deleted message is shown in chat view for public chat")
self.errors.verify_no_errors()
+35 -24
View File
@@ -1,26 +1,35 @@
import emoji
import random
from dateutil import parser
from tests import marks
from tests.base_test_case import MultipleDeviceTestCase, SingleDeviceTestCase
from views.sign_in_view import SignInView
from datetime import timedelta
from time import sleep
import emoji
import pytest
from dateutil import parser
class TestPublicChatMultipleDevice(MultipleDeviceTestCase):
from tests import marks
from tests.base_test_case import MultipleDeviceTestCase, SingleDeviceTestCase, create_shared_drivers, \
MultipleSharedDeviceTestCase
from views.home_view import HomeView
from views.sign_in_view import SignInView
@pytest.mark.xdist_group(name="public_chat")
class TestPublicChatMultipleDeviceMerged(MultipleSharedDeviceTestCase):
@classmethod
def setup_class(cls):
cls.drivers, cls.loop = create_shared_drivers(2)
device_1, device_2 = SignInView(cls.drivers[0]), SignInView(cls.drivers[1])
home_1, home_2 = device_1.create_user(), device_2.create_user()
profile_1 = home_1.profile_button.click()
cls.username_1 = profile_1.default_username_text.text
profile_1.home_button.click()
home_2.home_button.click()
@marks.testrail_id(5313)
@marks.critical
def test_public_chat_messaging_emojis_timestamps(self):
self.create_drivers(2)
device_1, device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
home_1, home_2 = device_1.create_user(), device_2.create_user()
profile_1 = home_1.profile_button.click()
default_username_1 = profile_1.default_username_text.text
profile_1.home_button.click()
home_2.home_button.click()
home_1, home_2 = HomeView(self.drivers[0]), HomeView(self.drivers[1])
home_1.just_fyi("Check preselected chats, redirect to status chat")
home_1.plus_button.click_until_presence_of_element(home_1.join_public_chat_button)
home_1.join_public_chat_button.click()
@@ -50,8 +59,8 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase):
if timestamp not in sent_time_variants:
self.errors.append(
"Timestamp is not shown, expected '%s', in fact '%s'" % (sent_time_variants.join(','), timestamp))
if chat_2.chat_element_by_text(message).username.text != default_username_1:
self.errors.append("Default username '%s' is not shown next to the received message" % default_username_1)
if chat_2.chat_element_by_text(message).username.text != self.username_1:
self.errors.append("Default username '%s' is not shown next to the received message" % self.username_1)
chat_1.send_message(emoji_message)
for chat in chat_1, chat_2:
@@ -63,13 +72,11 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase):
@marks.testrail_id(5360)
@marks.critical
def test_unread_messages_counter_public_chat(self):
self.create_drivers(2)
driver_2 = self.drivers[1]
home_1, home_2 = SignInView(self.drivers[0]).create_user(), SignInView(self.drivers[1]).create_user()
profile_1 = home_1.profile_button.click()
username_1 = profile_1.default_username_text.text
profile_1.home_button.click()
home_1, home_2 = HomeView(self.drivers[0]), HomeView(self.drivers[1])
home_1.get_back_to_home_view()
home_2.get_back_to_home_view()
home_1.home_button.click()
home_2.home_button.click()
chat_name = home_1.get_random_chat_name()
chat_1, chat_2 = home_1.join_public_chat(chat_name), home_2.join_public_chat(chat_name)
chat_1.send_message('пиу')
@@ -87,7 +94,7 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase):
home_1.just_fyi("Check unread message counter when mentioned in public chat")
chat_2 = home_2.get_chat_view()
chat_2.select_mention_from_suggestion_list(username_1, username_1[:2])
chat_2.select_mention_from_suggestion_list(self.username_1, self.username_1[:2])
chat_2.send_message_button.click()
chat_element.new_messages_counter.wait_for_element(30)
chat_element.new_messages_counter.wait_for_element_text("1", 60)
@@ -104,6 +111,7 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase):
chat_2.chat_element_by_text(message_2).wait_for_element(20)
home_2.just_fyi("Check that unread message indicator is not reappeared after relogin")
driver_2 = self.drivers[1]
driver_2.close_app()
driver_2.launch_app()
SignInView(driver_2).sign_in()
@@ -112,6 +120,9 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase):
self.errors.append('New messages counter is shown after relogin')
self.errors.verify_no_errors()
class TestPublicChatMultipleDevice(MultipleDeviceTestCase):
@marks.testrail_id(6270)
@marks.medium
def test_mark_all_messages_as_read_public_chat(self):
+5 -2
View File
@@ -13,6 +13,7 @@ import views.upgrade_dbs.group.data as group
class TestUpgradeApplication(SingleDeviceTestCase):
@marks.testrail_id(6284)
@marks.flaky
def test_unread_previews_public_chat_version_upgrade(self):
sign_in = SignInView(self.driver)
unread_one_to_one_name, unread_public_name = 'All Whopping Dassierat', '#before-upgrade'
@@ -117,8 +118,8 @@ class TestUpgradeApplication(SingleDeviceTestCase):
public_chat = home.get_chat(mention).click()
public_chat.scroll_to_start_of_history()
pub_chat_data = chats[mention]
public_chat.element_starts_with_text(pub_chat_data['reply']).scroll_to_element()
public_replied_message = public_chat.chat_element_by_text(pub_chat_data['reply'])
public_replied_message.scroll_to_element()
if pub_chat_data['long'] not in public_replied_message.replied_message_text:
self.errors.append("Reply is not present in message received in public chat %s after upgrade" % mention)
public_chat.element_starts_with_text(pub_chat_data['mention']).scroll_to_element()
@@ -477,6 +478,7 @@ class TestUpgradeMultipleApplication(MultipleDeviceTestCase):
self.errors.verify_no_errors()
@marks.testrail_id(695812)
@marks.flaky
def test_devices_activity_centre_profile_settings_upgrade(self):
self.create_drivers(2)
user = ens_user
@@ -524,7 +526,8 @@ class TestUpgradeMultipleApplication(MultipleDeviceTestCase):
if not profile_1.accept_new_chats_from_contacts_only.is_element_displayed():
self.errors.append("Accept contacts from setting is not preserved after upgrade!")
profile_1.profile_button.click()
profile_1.appearance_button.click()
profile_1.privacy_and_security_button.click()
profile_1.show_profile_pictures_of.scroll_to_element()
if not profile_1.show_profile_pictures_of.is_element_image_similar_to_template('block_dark.png'):
self.errors.append('Dark mode is not applied!')
if not profile_1.element_by_translation_id("everyone").is_element_displayed():
+167 -89
View File
@@ -19,47 +19,89 @@ from tests import test_suite_data, start_threads, appium_container, pytest_confi
import base64
from re import findall
sauce_username = environ.get('SAUCE_USERNAME')
sauce_access_key = environ.get('SAUCE_ACCESS_KEY')
executor_sauce_lab = 'http://%s:%s@ondemand.saucelabs.com:80/wd/hub' % (sauce_username, sauce_access_key)
executor_local = 'http://localhost:4723/wd/hub'
implicit_wait = 5
def get_capabilities_local():
desired_caps = dict()
if pytest_config_global['docker']:
# apk is in shared volume directory
apk = '/root/shared_volume/%s' % pytest_config_global['apk']
else:
apk = pytest_config_global['apk']
desired_caps['app'] = apk
desired_caps['deviceName'] = 'nexus_5'
desired_caps['platformName'] = 'Android'
desired_caps['appiumVersion'] = '1.9.1'
desired_caps['platformVersion'] = '10.0'
desired_caps['newCommandTimeout'] = 600
desired_caps['fullReset'] = False
desired_caps['unicodeKeyboard'] = True
desired_caps['automationName'] = 'UiAutomator2'
desired_caps['setWebContentDebuggingEnabled'] = True
return desired_caps
def add_local_devices_to_capabilities():
updated_capabilities = list()
raw_out = re.split(r'[\r\\n]+', str(subprocess.check_output(['adb', 'devices'])).rstrip())
for line in raw_out[1:]:
serial = re.findall(r"(([\d.\d:]*\d+)|\bemulator-\d+)", line)
if serial:
capabilities = get_capabilities_local()
capabilities['udid'] = serial[0][0]
updated_capabilities.append(capabilities)
return updated_capabilities
def get_capabilities_sauce_lab():
desired_caps = dict()
desired_caps['app'] = 'sauce-storage:' + test_suite_data.apk_name
desired_caps['build'] = pytest_config_global['build']
desired_caps['name'] = test_suite_data.current_test.name
desired_caps['platformName'] = 'Android'
desired_caps['appiumVersion'] = '1.18.1'
desired_caps['platformVersion'] = '10.0'
desired_caps['deviceName'] = 'Android GoogleAPI Emulator'
desired_caps['deviceOrientation'] = "portrait"
desired_caps['commandTimeout'] = 600
desired_caps['idleTimeout'] = 600
desired_caps['unicodeKeyboard'] = True
desired_caps['automationName'] = 'UiAutomator2'
desired_caps['setWebContentDebuggingEnabled'] = True
desired_caps['ignoreUnimportantViews'] = False
desired_caps['enableNotificationListener'] = True
desired_caps['maxDuration'] = 1800
return desired_caps
def update_capabilities_sauce_lab(new_capabilities: dict):
caps = get_capabilities_sauce_lab().copy()
caps.update(new_capabilities)
return caps
class AbstractTestCase:
__metaclass__ = ABCMeta
@property
def sauce_username(self):
return environ.get('SAUCE_USERNAME')
@property
def sauce_access_key(self):
return environ.get('SAUCE_ACCESS_KEY')
@property
def executor_sauce_lab(self):
return 'http://%s:%s@ondemand.saucelabs.com:80/wd/hub' % (self.sauce_username, self.sauce_access_key)
@property
def executor_local(self):
return 'http://localhost:4723/wd/hub'
@staticmethod
def print_sauce_lab_info(driver):
def print_sauce_lab_info(self, driver):
sys.stdout = sys.stderr
print("SauceOnDemandSessionID=%s job-name=%s" % (driver.session_id,
pytest_config_global['build']))
@staticmethod
def get_translation_by_key(key):
def get_translation_by_key(self, key):
return transl[key]
def add_local_devices_to_capabilities(self):
updated_capabilities = list()
raw_out = re.split(r'[\r\\n]+', str(subprocess.check_output(['adb', 'devices'])).rstrip())
for line in raw_out[1:]:
serial = re.findall(r"(([\d.\d:]*\d+)|\bemulator-\d+)", line)
if serial:
capabilities = self.capabilities_local
capabilities['udid'] = serial[0][0]
updated_capabilities.append(capabilities)
return updated_capabilities
@property
def app_path(self):
app_path = '/storage/emulated/0/Android/data/im.status.ethereum.pr/files/Download/' if findall(r'pr\d\d\d\d\d',
@@ -71,53 +113,6 @@ class AbstractTestCase:
def geth_path(self):
return self.app_path + 'geth.log'
@property
def capabilities_sauce_lab(self):
desired_caps = dict()
desired_caps['app'] = 'sauce-storage:' + test_suite_data.apk_name
desired_caps['build'] = pytest_config_global['build']
desired_caps['name'] = test_suite_data.current_test.name
desired_caps['platformName'] = 'Android'
desired_caps['appiumVersion'] = '1.18.1'
desired_caps['platformVersion'] = '10.0'
desired_caps['deviceName'] = 'Android GoogleAPI Emulator'
desired_caps['deviceOrientation'] = "portrait"
desired_caps['commandTimeout'] = 600
desired_caps['idleTimeout'] = 600
desired_caps['unicodeKeyboard'] = True
desired_caps['automationName'] = 'UiAutomator2'
desired_caps['setWebContentDebuggingEnabled'] = True
desired_caps['ignoreUnimportantViews'] = False
desired_caps['enableNotificationListener'] = True
desired_caps['maxDuration'] = 1800
return desired_caps
def update_capabilities_sauce_lab(self, new_capabilities: dict):
caps = self.capabilities_sauce_lab.copy()
caps.update(new_capabilities)
return caps
@property
def capabilities_local(self):
desired_caps = dict()
if pytest_config_global['docker']:
# apk is in shared volume directory
apk = '/root/shared_volume/%s' % pytest_config_global['apk']
else:
apk = pytest_config_global['apk']
desired_caps['app'] = apk
desired_caps['deviceName'] = 'nexus_5'
desired_caps['platformName'] = 'Android'
desired_caps['appiumVersion'] = '1.9.1'
desired_caps['platformVersion'] = pytest_config_global['platform_version']
desired_caps['newCommandTimeout'] = 600
desired_caps['fullReset'] = False
desired_caps['unicodeKeyboard'] = True
desired_caps['automationName'] = 'UiAutomator2'
desired_caps['setWebContentDebuggingEnabled'] = True
return desired_caps
@abstractmethod
def setup_method(self, method):
raise NotImplementedError('Should be overridden from a child class')
@@ -130,10 +125,6 @@ class AbstractTestCase:
def environment(self):
return pytest_config_global['env']
@property
def implicitly_wait(self):
return 5
network_api = NetworkApi()
github_report = GithubHtmlReport()
@@ -197,13 +188,13 @@ class SingleDeviceTestCase(AbstractTestCase):
appium_container.start_appium_container(pytest_config_global['docker_shared_volume'])
appium_container.connect_device(pytest_config_global['device_ip'])
(executor, capabilities) = (self.executor_sauce_lab, self.capabilities_sauce_lab) if \
self.environment == 'sauce' else (self.executor_local, self.capabilities_local)
(executor, capabilities) = (executor_sauce_lab, get_capabilities_sauce_lab()) if \
self.environment == 'sauce' else (executor_local, get_capabilities_local())
for key, value in kwargs.items():
capabilities[key] = value
self.driver = Driver(executor, capabilities)
test_suite_data.current_test.testruns[-1].jobs[self.driver.session_id] = 1
self.driver.implicitly_wait(self.implicitly_wait)
self.driver.implicitly_wait(implicit_wait)
self.errors = Errors()
if pytest_config_global['docker']:
@@ -241,7 +232,7 @@ class LocalMultipleDeviceTestCase(AbstractTestCase):
def teardown_method(self, method):
for driver in self.drivers:
try:
self.add_alert_text_to_report(driver)
self.add_alert_text_to_report(self.drivers[driver])
self.drivers[driver].quit()
except WebDriverException:
pass
@@ -263,12 +254,12 @@ class SauceMultipleDeviceTestCase(AbstractTestCase):
self.drivers = self.loop.run_until_complete(start_threads(quantity,
Driver,
self.drivers,
self.executor_sauce_lab,
self.update_capabilities_sauce_lab(capabilities)))
executor_sauce_lab,
update_capabilities_sauce_lab(capabilities)))
for driver in range(quantity):
test_suite_data.current_test.testruns[-1].jobs[self.drivers[driver].session_id] = driver + 1
self.drivers[driver].implicitly_wait(
custom_implicitly_wait if custom_implicitly_wait else self.implicitly_wait)
custom_implicitly_wait if custom_implicitly_wait else implicit_wait)
def teardown_method(self, method):
geth_names, geth_contents = [], []
@@ -290,10 +281,97 @@ class SauceMultipleDeviceTestCase(AbstractTestCase):
cls.loop.close()
def create_shared_drivers(quantity):
drivers = dict()
if pytest_config_global['env'] == 'local':
capabilities = add_local_devices_to_capabilities()
for i in range(quantity):
driver = Driver(executor_local, capabilities[i])
test_suite_data.current_test.testruns[-1].jobs[driver.session_id] = i + 1
driver.implicitly_wait(implicit_wait)
drivers[i] = driver
loop = None
else:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
capabilities = {'maxDuration': 1800}
drivers = loop.run_until_complete(start_threads(quantity,
Driver,
drivers,
executor_sauce_lab,
update_capabilities_sauce_lab(capabilities)))
for i in range(quantity):
test_suite_data.current_test.testruns[-1].jobs[drivers[i].session_id] = i + 1
drivers[i].implicitly_wait(implicit_wait)
return drivers, loop
class LocalSharedMultipleDeviceTestCase(AbstractTestCase):
def setup_method(self, method):
jobs = test_suite_data.current_test.testruns[-1].jobs
if not jobs:
for index, driver in self.drivers.items():
jobs[driver.session_id] = index + 1
self.errors = Errors()
def teardown_method(self, method):
for driver in self.drivers:
try:
self.add_alert_text_to_report(self.drivers[driver])
except WebDriverException:
pass
@classmethod
def teardown_class(cls):
for driver in cls.drivers:
try:
cls.drivers[driver].quit()
except WebDriverException:
pass
class SauceSharedMultipleDeviceTestCase(AbstractTestCase):
def setup_method(self, method):
jobs = test_suite_data.current_test.testruns[-1].jobs
if not jobs:
for index, driver in self.drivers.items():
jobs[driver.session_id] = index + 1
self.errors = Errors()
def teardown_method(self, method):
geth_names, geth_contents = [], []
for driver in self.drivers:
try:
self.print_sauce_lab_info(self.drivers[driver])
self.add_alert_text_to_report(self.drivers[driver])
geth_names.append(
'%s_geth%s.log' % (test_suite_data.current_test.name, str(self.drivers[driver].number)))
geth_contents.append(self.pull_geth(self.drivers[driver]))
except (WebDriverException, AttributeError):
pass
finally:
geth = {geth_names[i]: geth_contents[i] for i in range(len(geth_names))}
self.github_report.save_test(test_suite_data.current_test, geth)
@classmethod
def teardown_class(cls):
for driver in cls.drivers:
try:
cls.drivers[driver].quit()
except WebDriverException:
pass
cls.loop.close()
if pytest_config_global['env'] == 'local':
MultipleDeviceTestCase = LocalMultipleDeviceTestCase
MultipleSharedDeviceTestCase = LocalSharedMultipleDeviceTestCase
else:
MultipleDeviceTestCase = SauceMultipleDeviceTestCase
MultipleSharedDeviceTestCase = SauceSharedMultipleDeviceTestCase
class NoDeviceTestCase(AbstractTestCase):
+2 -1
View File
@@ -282,7 +282,8 @@ def pytest_runtest_protocol(item, nextitem):
for i in range(rerun_count):
reports = runtestprotocol(item, nextitem=nextitem)
for report in reports:
if report.failed and should_rerun_test(report.longreprtext):
is_in_group = [i for i in item.iter_markers(name='xdist_group')]
if report.failed and should_rerun_test(report.longreprtext) and not is_in_group:
break # rerun
else:
return True # no need to rerun
+6
View File
@@ -399,3 +399,9 @@ class SilentButton(Button):
def text(self):
text = self.find_element().text
return text
class CheckBox(Button):
def click(self):
super(CheckBox, self).click_until_presence_of_element(Button(self.driver, accessibility_id="checkbox-on"))
return self.navigate()
+18 -2
View File
@@ -13,7 +13,7 @@ from selenium.common.exceptions import NoSuchElementException, TimeoutException
from support.device_apps import start_web_browser
from tests import common_password, pytest_config_global, transl
from views.base_element import Button, BaseElement, EditBox, Text
from views.base_element import Button, BaseElement, EditBox, Text, CheckBox
class BackButton(Button):
@@ -26,6 +26,7 @@ class BackButton(Button):
return self.navigate()
class AllowButton(Button):
def __init__(self, driver):
super().__init__(driver, translation_id="allow", uppercase=True)
@@ -191,9 +192,21 @@ class AirplaneModeButton(Button):
def __init__(self, driver):
super().__init__(driver, accessibility_id="Airplane mode")
def click(self):
def open_quick_action_menu(self):
action = TouchAction(self.driver)
action.press(None, 200, 0).move_to(None, 200, 300).perform()
def click(self):
counter = 0
desired_element = AirplaneModeButton(self.driver)
while not desired_element.is_element_present() and counter <= 3:
try:
self.open_quick_action_menu()
desired_element.wait_for_element(5)
except (NoSuchElementException, TimeoutException):
counter += 1
else:
self.driver.info("%s element not found" % desired_element.name)
super(AirplaneModeButton, self).click()
self.driver.press_keycode(4)
@@ -252,6 +265,9 @@ class BaseView(object):
self.qr_code_image = Button(self.driver, accessibility_id="qr-code-image")
self.sign_in_phrase = SignInPhraseText(self.driver)
# checkboxes and toggles
self.checkbox_button = CheckBox(self.driver, accessibility_id="checkbox-off")
# external browser
self.open_in_status_button = OpenInStatusButton(self.driver)
+2 -3
View File
@@ -1,6 +1,6 @@
import time
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from views.base_element import Button, Text, BaseElement, SilentButton
from views.base_element import Button, Text, BaseElement, SilentButton, CheckBox
from views.base_view import BaseView
from tests import test_dapp_url
@@ -211,8 +211,7 @@ class HomeView(BaseView):
# Sync using mobile data bottom sheet
self.continue_syncing_button = Button(self.driver, accessibility_id="mobile-network-continue-syncing")
self.stop_syncing_button = Button(self.driver, accessibility_id="mobile-network-stop-syncing")
self.remember_my_choice_checkbox = Button(self.driver, xpath="//*[@content-desc='remember-choice']"
"//*[@content-desc='checkbox']")
self.remember_my_choice_checkbox = CheckBox(self.driver, accessibility_id=":checkbox-on")
# Connection status bottom sheet
self.connected_to_n_peers_text = Text(self.driver, accessibility_id="connected-to-n-peers")
+1 -7
View File
@@ -144,7 +144,7 @@ class SignInView(BaseView):
self.first_username_on_choose_chat_name = Text(self.driver,
xpath="//*[@content-desc='select-account-button-0']//android.widget.TextView[1]")
self.get_keycard_banner = Button(self.driver, translation_id="get-a-keycard")
self.accept_tos_checkbox = Button(self.driver, xpath="//android.widget.CheckBox[@content-desc='checkbox']")
self.accept_tos_checkbox = self.checkbox_button
# keycard recovery
self.recover_with_keycard_button = Button(self.driver, accessibility_id="recover-with-keycard-button")
@@ -178,9 +178,6 @@ class SignInView(BaseView):
if not second_user:
self.accept_tos_checkbox.click()
self.get_started_button.click_until_presence_of_element(self.generate_key_button)
if not self.generate_key_button.is_element_displayed():
[button.click() for button in
[self.accept_tos_checkbox, self.get_started_button, self.generate_key_button]]
self.generate_key_button.click()
self.next_button.click_until_absense_of_element(self.element_by_translation_id("intro-wizard-title2"))
@@ -209,9 +206,6 @@ class SignInView(BaseView):
if not second_user:
self.accept_tos_checkbox.click()
self.get_started_button.click_until_presence_of_element(self.access_key_button)
if not self.access_key_button.is_element_displayed():
self.accept_tos_checkbox.click()
self.get_started_button.click()
self.access_key_button.click()
self.enter_seed_phrase_button.click()
self.seedphrase_input.click()
+6 -6
View File
@@ -55,21 +55,21 @@ chats = {
'preview':'Request address for transaction accepted',
'messages': {
'audio': {
'length':'00:10',
'timestamp' : '1:02 PM'
'length': '00:10',
'timestamp': '1:02 PM'
},
},
'commands':{
'incoming_ETH_shared':{
'value':'0.01 ETH',
'incoming_ETH_shared': {
'value': '0.01 ETHro',
'status': "Shared 'Ethereum account'"
},
'incoming_ETH_confirmed': {
'value': '0.1 ETH',
'value': '0.1 ETHro',
'status': transl["status-confirmed"]
},
'incoming_ETH_declined': {
'value': '20 ETH',
'value': '20 ETHro',
'status': transl["transaction-declined"]
},
'incoming_STT_confirmed': {
+8 -7
View File
@@ -1,7 +1,7 @@
import time
from tests import common_password
from views.base_element import Button, Text, EditBox, SilentButton
from views.base_element import Button, Text, EditBox, SilentButton, CheckBox
from views.base_view import BaseView
@@ -14,12 +14,13 @@ class TransactionHistoryButton(Button):
return TransactionsView(self.driver)
class AssetCheckBox(SilentButton):
class AssetCheckBox(CheckBox):
def __init__(self, driver, asset_name):
super().__init__(driver, xpath="//*[@text='%s']" % asset_name)
def click(self):
self.scroll_to_element(12).click()
self.scroll_to_element(12)
super().click()
class BackupRecoveryPhrase(Button):
@@ -101,12 +102,12 @@ class WalletView(BaseView):
self.manage_assets_button = Button(self.driver, accessibility_id="wallet-manage-assets")
self.manage_accounts_button = Button(self.driver, accessibility_id="wallet-manage-accounts")
self.scan_tokens_button = Button(self.driver, accessibility_id="wallet-scan-token")
self.stt_check_box = Button(self.driver,
xpath="//*[@text='STT']/../android.view.ViewGroup[@content-desc='checkbox']")
self.stt_check_box = CheckBox(self.driver,
xpath="//*[@text='STT']/../android.view.ViewGroup[@content-desc='checkbox-off']")
self.all_assets_full_names = Text(self.driver,
xpath="//*[@content-desc='checkbox']/../android.widget.TextView[1]")
xpath="//*[@content-desc='checkbox-off']/../android.widget.TextView[1]")
self.all_assets_symbols = Button(self.driver,
xpath="//*[@content-desc='checkbox']/../android.widget.TextView[2]")
xpath="//*[@content-desc='checkbox-off']/../android.widget.TextView[2]")
self.currency_item_text = Text(self.driver, xpath="//*[@content-desc='currency-item']//android.widget.TextView")
self.address_text = Text(self.driver, accessibility_id="address-text")